mirror of
https://github.com/trailbaseio/trailbase.git
synced 2025-12-16 15:15:51 -06:00
Add WASM host-runtime to TrailBase and rebrand relevant command-line flags.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# Build artifacts
|
||||
target/
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# Dart workspace artifacts
|
||||
.dart_tool
|
||||
|
||||
1160
Cargo.lock
generated
1160
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
21
Cargo.toml
@@ -13,6 +13,9 @@ members = [
|
||||
"crates/schema",
|
||||
"crates/sqlean",
|
||||
"crates/sqlite",
|
||||
"crates/wasi-keyvalue",
|
||||
"crates/wasm-runtime-host",
|
||||
"crates/wasm-runtime-common",
|
||||
"docs/examples/record_api_rs",
|
||||
"examples/custom-binary",
|
||||
]
|
||||
@@ -25,8 +28,13 @@ default-members = [
|
||||
"crates/extension",
|
||||
"crates/js-runtime",
|
||||
"crates/qs",
|
||||
"crates/refinery",
|
||||
"crates/schema",
|
||||
"crates/sqlean",
|
||||
"crates/sqlite",
|
||||
"crates/wasi-keyvalue",
|
||||
"crates/wasm-runtime-host",
|
||||
"crates/wasm-runtime-common",
|
||||
]
|
||||
|
||||
# https://doc.rust-lang.org/cargo/reference/profiles.html
|
||||
@@ -66,15 +74,24 @@ rust-embed = { version = "8.4.0", default-features = false, features = ["mime-gu
|
||||
tokio = { version = "^1.38.0", default-features = false, features = ["macros", "net", "rt-multi-thread", "fs", "signal", "time", "sync"] }
|
||||
tracing = { version = "0.1.40", default-features = false }
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = ["smallvec", "std", "fmt", "json"] }
|
||||
trailbase = { path = "crates/core", version = "0.2.0" }
|
||||
trailbase-assets = { path = "crates/assets", version = "0.2.0" }
|
||||
trailbase-build = { path = "crates/build", version = "0.1.1" }
|
||||
trailbase-client = { path = "crates/client", version = "0.5.0" }
|
||||
trailbase-sqlean = { path = "crates/sqlean", version = "0.0.3" }
|
||||
trailbase-extension = { path = "crates/extension", version = "0.3.0" }
|
||||
trailbase-js = { path = "crates/js-runtime", version = "0.2.0" }
|
||||
trailbase-qs = { path = "crates/qs", version = "0.1.0" }
|
||||
trailbase-refinery = { path = "crates/refinery", version = "0.1.0" }
|
||||
trailbase-schema = { path = "crates/schema", version = "0.1.0" }
|
||||
trailbase-sqlean = { path = "crates/sqlean", version = "0.0.3" }
|
||||
trailbase-sqlite = { path = "crates/sqlite", version = "0.3.0" }
|
||||
trailbase = { path = "crates/core", version = "0.2.0" }
|
||||
trailbase-wasi-keyvalue = { path = "crates/wasi-keyvalue", version = "0.1.0" }
|
||||
trailbase-wasm = { path = "crates/wasm-runtime-guest", version = "0.1.0" }
|
||||
trailbase-wasm-common = { path = "crates/wasm-runtime-common", version = "0.1.0" }
|
||||
trailbase-wasm-runtime-host = { path = "crates/wasm-runtime-host", version = "0.1.0" }
|
||||
ts-rs = { version = "11", features = ["uuid-impl", "serde-json-impl"] }
|
||||
uuid = { version = "1", default-features = false, features = ["std", "v4", "v7", "serde"] }
|
||||
wasmtime = "36.0.1"
|
||||
wasmtime-wasi = { version = "36.0.1", default-features = false, features = [] }
|
||||
wasmtime-wasi-http = "36.0.1"
|
||||
wasmtime-wasi-io = "36.0.1"
|
||||
|
||||
@@ -174,7 +174,7 @@ Future<Process> initTrailBase() async {
|
||||
'run',
|
||||
'--address=${address}',
|
||||
// We want at least some parallelism to experience isolate-local state.
|
||||
'--js-runtime-threads=2',
|
||||
'--runtime-threads=2',
|
||||
]);
|
||||
|
||||
final dio = Dio();
|
||||
|
||||
@@ -58,7 +58,7 @@ public class ClientTestFixture : IDisposable {
|
||||
process = new Process();
|
||||
process.StartInfo.WorkingDirectory = projectDirectory;
|
||||
process.StartInfo.FileName = "cargo";
|
||||
process.StartInfo.Arguments = $"run -- --data-dir ../../testfixture run -a {address} --js-runtime-threads 2";
|
||||
process.StartInfo.Arguments = $"run -- --data-dir ../../testfixture run -a {address} --runtime-threads 2";
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.Start();
|
||||
|
||||
@@ -52,7 +52,7 @@ func startTrailBase() (*exec.Cmd, error) {
|
||||
fmt.Sprint("--data-dir=", traildepot),
|
||||
"run",
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", PORT),
|
||||
"--js-runtime-threads=2",
|
||||
"--runtime-threads=2",
|
||||
}
|
||||
cmd := buildCommand("cargo", cwd, args...)
|
||||
cmd.Start()
|
||||
|
||||
@@ -38,7 +38,7 @@ class TrailBaseFixture:
|
||||
"run",
|
||||
"-a",
|
||||
address,
|
||||
"--js-runtime-threads",
|
||||
"--runtime-threads",
|
||||
"1",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -56,7 +56,7 @@ func startTrailBase() async throws -> ProcessIdentifier {
|
||||
"--data-dir=\(depotPath)",
|
||||
"run",
|
||||
"--address=127.0.0.1:\(PORT)",
|
||||
"--js-runtime-threads=2",
|
||||
"--runtime-threads=2",
|
||||
]
|
||||
|
||||
let process = try Subprocess.runDetached(
|
||||
|
||||
@@ -117,7 +117,18 @@ addRoute(
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
throw new HttpError(StatusCodes.BAD_REQUEST, "Missing ?url param");
|
||||
throw new HttpError(StatusCodes.BAD_REQUEST, `Missing ?url param: ${req.params}`);
|
||||
}),
|
||||
);
|
||||
|
||||
addRoute(
|
||||
"GET",
|
||||
"/fibonacci",
|
||||
stringHandler((req: StringRequestType) => {
|
||||
const uri: ParsedPath = parsePath(req.uri);
|
||||
const n = uri.query.get("n");
|
||||
|
||||
return fibonacci(n ? parseInt(n) : 40).toString();
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -160,17 +171,28 @@ class Completer<T> {
|
||||
|
||||
const completer = new Completer<string>();
|
||||
|
||||
addCronCallback("JS-registered Job", "@hourly", async () => {
|
||||
console.info("JS-registered cron job reporting for duty 🚀");
|
||||
});
|
||||
|
||||
addPeriodicCallback(100, (cancel) => {
|
||||
completer.complete("resolved");
|
||||
cancel();
|
||||
});
|
||||
|
||||
addCronCallback("JS-registered Job", "@hourly", async () => {
|
||||
console.info("JS-registered cron job reporting for duty 🚀");
|
||||
});
|
||||
|
||||
addRoute(
|
||||
"GET",
|
||||
"/await",
|
||||
stringHandler(async (_req) => await completer.promise),
|
||||
);
|
||||
|
||||
function fibonacci(num: number): number {
|
||||
switch (num) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return 1;
|
||||
default:
|
||||
return fibonacci(num - 1) + fibonacci(num - 2);
|
||||
}
|
||||
}
|
||||
BIN
client/testfixture/wasm/wasm_rust_guest_testfixture.wasm
Normal file
BIN
client/testfixture/wasm/wasm_rust_guest_testfixture.wasm
Normal file
Binary file not shown.
5
crates/assets/js/bindings/HttpContext.ts
Normal file
5
crates/assets/js/bindings/HttpContext.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HttpContextKind } from "./HttpContextKind";
|
||||
import type { HttpContextUser } from "./HttpContextUser";
|
||||
|
||||
export type HttpContext = { kind: HttpContextKind, registered_path: string, path_params: Array<[string, string]>, user: HttpContextUser | null, };
|
||||
3
crates/assets/js/bindings/HttpContextKind.ts
Normal file
3
crates/assets/js/bindings/HttpContextKind.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type HttpContextKind = "Http" | "Job";
|
||||
15
crates/assets/js/bindings/HttpContextUser.ts
Normal file
15
crates/assets/js/bindings/HttpContextUser.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type HttpContextUser = {
|
||||
/**
|
||||
* Url-safe Base64 encoded id of the current user.
|
||||
*/
|
||||
id: string,
|
||||
/**
|
||||
* E-mail of the current user.
|
||||
*/
|
||||
email: string,
|
||||
/**
|
||||
* The "expected" CSRF token as included in the auth token claims [User] was constructed from.
|
||||
*/
|
||||
csrf_token: string, };
|
||||
4
crates/assets/js/bindings/SqliteRequest.ts
Normal file
4
crates/assets/js/bindings/SqliteRequest.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "./serde_json/JsonValue";
|
||||
|
||||
export type SqliteRequest = { query: string, params: Array<JsonValue>, };
|
||||
4
crates/assets/js/bindings/SqliteResponse.ts
Normal file
4
crates/assets/js/bindings/SqliteResponse.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "./serde_json/JsonValue";
|
||||
|
||||
export type SqliteResponse = { "Query": { rows: Array<Array<JsonValue>>, } } | { "Execute": { rows_affected: number, } } | { "Error": string } | "TxBegin" | "TxCommit" | "TxRollback";
|
||||
@@ -28,7 +28,7 @@ async function initTrailBase(): Promise<{ subprocess: Subprocess }> {
|
||||
cwd: root,
|
||||
stdout: process.stdout,
|
||||
stderr: process.stdout,
|
||||
})`cargo run -- --data-dir client/testfixture --public-url http://${ADDRESS} run -a ${ADDRESS} --js-runtime-threads 1`;
|
||||
})`cargo run -- --data-dir client/testfixture --public-url http://${ADDRESS} run -a ${ADDRESS} --runtime-threads 1`;
|
||||
|
||||
for (let i = 0; i < 100; ++i) {
|
||||
if ((subprocess.exitCode ?? 0) > 0) {
|
||||
|
||||
@@ -93,6 +93,10 @@ pub struct ServerArgs {
|
||||
#[arg(long, env)]
|
||||
pub public_dir: Option<String>,
|
||||
|
||||
/// Optional path to sandboxed FS root for WASM runtime.
|
||||
#[arg(long, env)]
|
||||
pub runtime_root_fs: Option<String>,
|
||||
|
||||
/// Optional path to MaxmindDB geoip database. Can be used to map logged IPs to a geo location.
|
||||
#[arg(long, env)]
|
||||
pub geoip_db_path: Option<String>,
|
||||
@@ -119,7 +123,7 @@ pub struct ServerArgs {
|
||||
|
||||
/// Number of JavaScript isolates/workers to start (Default: #cpus).
|
||||
#[arg(long, env)]
|
||||
pub js_runtime_threads: Option<usize>,
|
||||
pub runtime_threads: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
|
||||
@@ -73,13 +73,14 @@ async fn async_main() -> Result<(), BoxError> {
|
||||
address: cmd.address,
|
||||
admin_address: cmd.admin_address,
|
||||
public_dir: cmd.public_dir.map(|p| p.into()),
|
||||
runtime_root_fs: cmd.runtime_root_fs.map(|p| p.into()),
|
||||
geoip_db_path: cmd.geoip_db_path.map(|p| p.into()),
|
||||
log_responses: cmd.dev || cmd.stderr_logging,
|
||||
dev: cmd.dev,
|
||||
demo: cmd.demo,
|
||||
disable_auth_ui: cmd.disable_auth_ui,
|
||||
cors_allowed_origins: cmd.cors_allowed_origins,
|
||||
js_runtime_threads: cmd.js_runtime_threads,
|
||||
runtime_threads: cmd.runtime_threads,
|
||||
tls_key: None,
|
||||
tls_cert: None,
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ fn start_server() -> Result<Server, std::io::Error> {
|
||||
format!("--data-dir={depot_path}"),
|
||||
"run".to_string(),
|
||||
format!("--address=127.0.0.1:{PORT}"),
|
||||
"--js-runtime-threads=2".to_string(),
|
||||
"--runtime-threads=2".to_string(),
|
||||
];
|
||||
let child = std::process::Command::new("cargo")
|
||||
.args(&args)
|
||||
|
||||
@@ -7,7 +7,7 @@ rust-version = "1.86"
|
||||
description = "Package to use TrailBase as a framework"
|
||||
homepage = "https://trailbase.io"
|
||||
repository = "https://github.com/trailbaseio/trailbase"
|
||||
readme = "../README.md"
|
||||
readme = "../../README.md"
|
||||
exclude = [
|
||||
"benches/",
|
||||
"tests/",
|
||||
@@ -89,7 +89,9 @@ trailbase-qs = { workspace = true }
|
||||
trailbase-refinery = { workspace = true }
|
||||
trailbase-schema = { workspace = true }
|
||||
trailbase-sqlite = { workspace = true }
|
||||
ts-rs = { version = "11", features = ["uuid-impl", "serde-json-impl"] }
|
||||
trailbase-wasm-runtime-host = { workspace = true }
|
||||
trailbase-wasm-common = { workspace = true }
|
||||
ts-rs = { workspace = true }
|
||||
url = { version = "^2.4.1", default-features = false }
|
||||
utoipa = { version = "5.0.0-beta.0", features = ["axum_extras"] }
|
||||
uuid = { workspace = true }
|
||||
|
||||
@@ -4,6 +4,7 @@ use reactivate::{Merge, Reactive};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use trailbase_schema::QualifiedName;
|
||||
use trailbase_wasm_runtime_host::Runtime;
|
||||
|
||||
use crate::auth::jwt::JwtHelper;
|
||||
use crate::auth::options::AuthOptions;
|
||||
@@ -43,6 +44,8 @@ struct InternalState {
|
||||
|
||||
runtime: RuntimeHandle,
|
||||
|
||||
wasm_runtimes: Vec<Arc<Runtime>>,
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unused)]
|
||||
cleanup: Vec<Box<dyn std::any::Any + Send + Sync>>,
|
||||
@@ -52,6 +55,7 @@ pub(crate) struct AppStateArgs {
|
||||
pub data_dir: DataDir,
|
||||
pub public_url: Option<url::Url>,
|
||||
pub public_dir: Option<PathBuf>,
|
||||
pub runtime_root_fs: Option<PathBuf>,
|
||||
pub dev: bool,
|
||||
pub demo: bool,
|
||||
pub schema_metadata: SchemaMetadataCache,
|
||||
@@ -60,7 +64,7 @@ pub(crate) struct AppStateArgs {
|
||||
pub logs_conn: trailbase_sqlite::Connection,
|
||||
pub jwt: JwtHelper,
|
||||
pub object_store: Box<dyn ObjectStore + Send + Sync>,
|
||||
pub js_runtime_threads: Option<usize>,
|
||||
pub runtime_threads: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -124,7 +128,14 @@ impl AppState {
|
||||
object_store.clone(),
|
||||
);
|
||||
|
||||
let runtime = build_js_runtime(args.conn.clone(), args.js_runtime_threads);
|
||||
let runtime = build_js_runtime(args.conn.clone(), args.runtime_threads);
|
||||
let wasm_runtimes = crate::wasm::build_wasm_runtimes_for_components(
|
||||
args.runtime_threads,
|
||||
args.conn.clone(),
|
||||
args.data_dir.root().join("wasm"),
|
||||
args.runtime_root_fs,
|
||||
)
|
||||
.expect("startup");
|
||||
|
||||
AppState {
|
||||
state: Arc::new(InternalState {
|
||||
@@ -163,6 +174,7 @@ impl AppState {
|
||||
schema_metadata,
|
||||
object_store,
|
||||
runtime,
|
||||
wasm_runtimes,
|
||||
#[cfg(test)]
|
||||
cleanup: vec![],
|
||||
}),
|
||||
@@ -311,6 +323,10 @@ impl AppState {
|
||||
pub(crate) fn script_runtime(&self) -> RuntimeHandle {
|
||||
return self.state.runtime.clone();
|
||||
}
|
||||
|
||||
pub(crate) fn wasm_runtimes(&self) -> &[Arc<Runtime>] {
|
||||
return &self.state.wasm_runtimes;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -458,6 +474,7 @@ pub async fn test_state(options: Option<TestStateOptions>) -> anyhow::Result<App
|
||||
schema_metadata,
|
||||
object_store,
|
||||
runtime: build_js_runtime(conn, None),
|
||||
wasm_runtimes: vec![],
|
||||
cleanup: vec![Box::new(temp_dir)],
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ impl DbUser {
|
||||
|
||||
/// Representing an authenticated and *valid* user, as opposed to DbUser, which is merely an entry
|
||||
/// for any user including users that haven't been validated.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct User {
|
||||
/// Url-safe Base64 encoded id of the current user.
|
||||
pub id: String,
|
||||
|
||||
@@ -67,6 +67,7 @@ impl DataDir {
|
||||
self.migrations_path(),
|
||||
self.uploads_path(),
|
||||
self.key_path(),
|
||||
self.root().join("wasm/"),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -101,4 +102,13 @@ backups/
|
||||
data/
|
||||
secrets/
|
||||
uploads/
|
||||
wasm/
|
||||
scripts/
|
||||
|
||||
# Runtime files, will be overriden by `trail`.
|
||||
trailbase.d.ts
|
||||
trailbase.js
|
||||
|
||||
# Any potential MaxMind GeoIP dbs.
|
||||
*.mmdb
|
||||
"#;
|
||||
|
||||
@@ -7,6 +7,7 @@ use axum::response::{IntoResponse, Response};
|
||||
use futures_util::FutureExt;
|
||||
use log::*;
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -324,6 +325,7 @@ async fn install_routes_and_jobs(
|
||||
|
||||
pub(crate) async fn load_routes_and_jobs_from_js_modules(
|
||||
state: &AppState,
|
||||
scripts_dir: PathBuf,
|
||||
) -> Result<Option<Router<AppState>>, AnyError> {
|
||||
let runtime_handle = state.script_runtime();
|
||||
if runtime_handle.num_threads() == 0 {
|
||||
@@ -331,7 +333,6 @@ pub(crate) async fn load_routes_and_jobs_from_js_modules(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let scripts_dir = state.data_dir().root().join("scripts");
|
||||
let modules = match Module::load_dir(scripts_dir.clone()) {
|
||||
Ok(modules) => modules,
|
||||
Err(err) => {
|
||||
|
||||
@@ -22,6 +22,7 @@ mod scheduler;
|
||||
mod schema_metadata;
|
||||
mod server;
|
||||
mod transaction;
|
||||
mod wasm;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
@@ -61,8 +61,9 @@ mod tests {
|
||||
pub fn to_message(v: serde_json::Value) -> Message {
|
||||
return match v {
|
||||
serde_json::Value::Object(ref obj) => {
|
||||
let keys: Vec<&str> = obj.keys().map(|s| s.as_str()).collect();
|
||||
assert_eq!(keys, vec!["mid", "room", "data", "table"], "Got: {keys:?}");
|
||||
let mut keys: Vec<&str> = obj.keys().map(|s| s.as_str()).collect();
|
||||
keys.sort();
|
||||
assert_eq!(keys, ["data", "mid", "room", "table"], "Got: {keys:?}");
|
||||
serde_json::from_value::<Message>(v).unwrap()
|
||||
}
|
||||
_ => panic!("expected object, got {v:?}"),
|
||||
|
||||
@@ -43,12 +43,13 @@ pub struct InitArgs {
|
||||
pub data_dir: DataDir,
|
||||
pub public_url: Option<url::Url>,
|
||||
pub public_dir: Option<PathBuf>,
|
||||
pub runtime_root_fs: Option<PathBuf>,
|
||||
pub geoip_db_path: Option<PathBuf>,
|
||||
|
||||
pub address: String,
|
||||
pub dev: bool,
|
||||
pub demo: bool,
|
||||
pub js_runtime_threads: Option<usize>,
|
||||
pub runtime_threads: Option<usize>,
|
||||
}
|
||||
|
||||
pub async fn init_app_state(args: InitArgs) -> Result<(bool, AppState), InitError> {
|
||||
@@ -138,6 +139,7 @@ pub async fn init_app_state(args: InitArgs) -> Result<(bool, AppState), InitErro
|
||||
data_dir: args.data_dir.clone(),
|
||||
public_url: args.public_url,
|
||||
public_dir: args.public_dir,
|
||||
runtime_root_fs: args.runtime_root_fs,
|
||||
dev: args.dev,
|
||||
demo: args.demo,
|
||||
schema_metadata,
|
||||
@@ -146,7 +148,7 @@ pub async fn init_app_state(args: InitArgs) -> Result<(bool, AppState), InitErro
|
||||
logs_conn,
|
||||
jwt,
|
||||
object_store,
|
||||
js_runtime_threads: args.js_runtime_threads,
|
||||
runtime_threads: args.runtime_threads,
|
||||
});
|
||||
|
||||
if new_db {
|
||||
|
||||
@@ -54,6 +54,9 @@ pub struct ServerOptions {
|
||||
/// Optional path to static assets that will be served at the HTTP root.
|
||||
pub public_dir: Option<PathBuf>,
|
||||
|
||||
/// Optional path to sandboxed FS root for WASM runtime.
|
||||
pub runtime_root_fs: Option<PathBuf>,
|
||||
|
||||
/// Optional path to MaxmindDB geoip database. Can be used to map logged IPs to a geo location.
|
||||
pub geoip_db_path: Option<PathBuf>,
|
||||
|
||||
@@ -75,7 +78,7 @@ pub struct ServerOptions {
|
||||
pub cors_allowed_origins: Vec<String>,
|
||||
|
||||
/// Number of V8 worker threads. If set to None, default of num available cores will be used.
|
||||
pub js_runtime_threads: Option<usize>,
|
||||
pub runtime_threads: Option<usize>,
|
||||
|
||||
/// TLS certificate path.
|
||||
pub tls_cert: Option<CertificateDer<'static>>,
|
||||
@@ -120,15 +123,20 @@ impl Server {
|
||||
date = version_info.commit_date.unwrap_or_default(),
|
||||
);
|
||||
|
||||
validate_path(opts.public_dir.as_ref())?;
|
||||
validate_path(opts.runtime_root_fs.as_ref())?;
|
||||
validate_path(opts.geoip_db_path.as_ref())?;
|
||||
|
||||
let (new_data_dir, state) = init::init_app_state(InitArgs {
|
||||
data_dir: opts.data_dir.clone(),
|
||||
public_url: opts.public_url.clone(),
|
||||
public_dir: opts.public_dir.clone(),
|
||||
runtime_root_fs: opts.runtime_root_fs.clone(),
|
||||
geoip_db_path: opts.geoip_db_path.clone(),
|
||||
address: opts.address.clone(),
|
||||
dev: opts.dev,
|
||||
demo: opts.demo,
|
||||
js_runtime_threads: opts.js_runtime_threads,
|
||||
runtime_threads: opts.runtime_threads,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -170,18 +178,31 @@ impl Server {
|
||||
.map_err(|err| InitError::CustomInit(err.to_string()))?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "v8")]
|
||||
let js_routes: Option<Router<AppState>> =
|
||||
crate::js::runtime::load_routes_and_jobs_from_js_modules(&state)
|
||||
.await
|
||||
.map_err(|err| InitError::ScriptError(err.to_string()))?;
|
||||
let mut custom_routers: Vec<Router<AppState>> = vec![];
|
||||
|
||||
#[cfg(not(feature = "v8"))]
|
||||
let js_routes: Option<Router<AppState>> = None;
|
||||
#[cfg(feature = "v8")]
|
||||
if let Some(js_router) = crate::js::runtime::load_routes_and_jobs_from_js_modules(
|
||||
&state,
|
||||
state.data_dir().root().join("scripts"),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| InitError::ScriptError(err.to_string()))?
|
||||
{
|
||||
custom_routers.push(js_router);
|
||||
}
|
||||
|
||||
for rt in state.wasm_runtimes() {
|
||||
if let Some(wasm_router) = crate::wasm::install_routes_and_jobs(&state, rt.clone())
|
||||
.await
|
||||
.map_err(|err| InitError::ScriptError(err.to_string()))?
|
||||
{
|
||||
custom_routers.push(wasm_router);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
state: state.clone(),
|
||||
main_router: Self::build_main_router(&state, &opts, js_routes).await,
|
||||
main_router: Self::build_main_router(&state, &opts, custom_routers).await,
|
||||
admin_router: Self::build_independent_admin_router(&state, &opts),
|
||||
tls: Self::load_tls(&opts),
|
||||
})
|
||||
@@ -200,6 +221,7 @@ impl Server {
|
||||
stream.recv().await;
|
||||
|
||||
// TODO: Re-load JS/TS.
|
||||
// TODO: Re-load WASM.
|
||||
info!("Received SIGHUP: re-apply migations then re-load config.");
|
||||
|
||||
// Re-apply migrations. This needs to happen before reloading the config, which is
|
||||
@@ -333,7 +355,7 @@ impl Server {
|
||||
async fn build_main_router(
|
||||
state: &AppState,
|
||||
opts: &ServerOptions,
|
||||
custom_router: Option<Router<AppState>>,
|
||||
custom_routers: Vec<Router<AppState>>,
|
||||
) -> (String, Router<()>) {
|
||||
let enable_transactions =
|
||||
state.access_config(|conn| conn.server.enable_record_transactions.unwrap_or(false));
|
||||
@@ -352,7 +374,7 @@ impl Server {
|
||||
router = router.merge(auth::auth_ui_router());
|
||||
}
|
||||
|
||||
if let Some(custom_router) = custom_router {
|
||||
for custom_router in custom_routers {
|
||||
router = router.merge(custom_router);
|
||||
}
|
||||
|
||||
@@ -579,6 +601,11 @@ async fn start_listen(
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "v8"))]
|
||||
tokio_rustls::rustls::crypto::ring::default_provider()
|
||||
.install_default()
|
||||
.expect("Failed to install TLS provider");
|
||||
|
||||
let server_config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(vec![cert], key)
|
||||
@@ -616,3 +643,12 @@ async fn start_listen(
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_path(path: Option<&PathBuf>) -> Result<(), InitError> {
|
||||
if let Some(path) = path {
|
||||
if !std::fs::exists(path)? {
|
||||
return Err(InitError::CustomInit(format!("Path not found: {path:?}")));
|
||||
};
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
230
crates/core/src/wasm/mod.rs
Normal file
230
crates/core/src/wasm/mod.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
use axum::Router;
|
||||
use axum::extract::{RawPathParams, Request};
|
||||
use bytes::Bytes;
|
||||
use http_body_util::{BodyExt, combinators::BoxBody};
|
||||
use hyper::StatusCode;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use trailbase_wasm_common::{HttpContext, HttpContextKind, HttpContextUser};
|
||||
use trailbase_wasm_runtime_host::exports::trailbase::runtime::init_endpoint::MethodType;
|
||||
use trailbase_wasm_runtime_host::{Error as WasmError, KvStore, Runtime};
|
||||
|
||||
use crate::AppState;
|
||||
use crate::User;
|
||||
|
||||
type AnyError = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
pub(crate) fn build_wasm_runtimes_for_components(
|
||||
n_threads: Option<usize>,
|
||||
conn: trailbase_sqlite::Connection,
|
||||
components_path: PathBuf,
|
||||
fs_root_path: Option<PathBuf>,
|
||||
) -> Result<Vec<Arc<Runtime>>, AnyError> {
|
||||
let shared_kv_store = KvStore::new();
|
||||
let mut runtimes: Vec<Arc<Runtime>> = vec![];
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir(components_path) {
|
||||
for entry in entries {
|
||||
let Ok(entry) = entry else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(metadata) = entry.metadata() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !metadata.is_file() {
|
||||
continue;
|
||||
}
|
||||
let path = entry.path();
|
||||
let Some(extension) = path.extension().and_then(|e| e.to_str()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if extension == "wasm" {
|
||||
let n_threads = n_threads
|
||||
.or(std::thread::available_parallelism().ok().map(|n| n.get()))
|
||||
.unwrap_or(1);
|
||||
|
||||
runtimes.push(Arc::new(Runtime::new(
|
||||
n_threads,
|
||||
path,
|
||||
conn.clone(),
|
||||
shared_kv_store.clone(),
|
||||
fs_root_path.clone(),
|
||||
)?));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if runtimes.is_empty() {
|
||||
log::debug!("No WASM component found");
|
||||
}
|
||||
|
||||
return Ok(runtimes);
|
||||
}
|
||||
|
||||
pub(crate) async fn install_routes_and_jobs(
|
||||
state: &AppState,
|
||||
runtime: Arc<Runtime>,
|
||||
) -> Result<Option<Router<AppState>>, AnyError> {
|
||||
let init_result = runtime
|
||||
.call(async |instance| {
|
||||
return instance.call_init().await;
|
||||
})
|
||||
.await??;
|
||||
|
||||
for (name, spec) in &init_result.job_handlers {
|
||||
let schedule = cron::Schedule::from_str(spec)?;
|
||||
let runtime = runtime.clone();
|
||||
let name_clone = name.to_string();
|
||||
|
||||
let Some(job) = state.jobs().new_job(
|
||||
None,
|
||||
name,
|
||||
schedule,
|
||||
crate::scheduler::build_callback(move || {
|
||||
let name = name_clone.clone();
|
||||
let runtime = runtime.clone();
|
||||
|
||||
return async move {
|
||||
runtime
|
||||
.call(async move |instance| -> Result<(), WasmError> {
|
||||
let request = hyper::Request::builder()
|
||||
// NOTE: We cannot use a custom-scheme, since the wasi http
|
||||
// implementation rejects everything but http and https.
|
||||
.uri(format!("http://__job/?name={name}"))
|
||||
.header(
|
||||
"__context",
|
||||
to_header_value(&HttpContext {
|
||||
kind: HttpContextKind::Job,
|
||||
registered_path: name.clone(),
|
||||
path_params: vec![],
|
||||
user: None,
|
||||
})?,
|
||||
)
|
||||
.body(empty())
|
||||
.unwrap_or_default();
|
||||
|
||||
instance.call_incoming_http_handler(request).await?;
|
||||
|
||||
return Ok(());
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok::<_, AnyError>(())
|
||||
};
|
||||
}),
|
||||
) else {
|
||||
return Err("Failed to add job".into());
|
||||
};
|
||||
|
||||
job.start();
|
||||
}
|
||||
|
||||
log::debug!("Got {} WASM routes", init_result.http_handlers.len());
|
||||
|
||||
let mut router = Router::<AppState>::new();
|
||||
for (method, path) in &init_result.http_handlers {
|
||||
let runtime = runtime.clone();
|
||||
|
||||
log::debug!("Installing WASM route: {method:?}: {path}");
|
||||
|
||||
let handler = {
|
||||
let path = path.clone();
|
||||
|
||||
move |params: RawPathParams, user: Option<User>, req: Request| async move {
|
||||
log::debug!(
|
||||
"Host received WASM HTTP request: {params:?}, {user:?}, {}",
|
||||
req.uri()
|
||||
);
|
||||
|
||||
let result = runtime
|
||||
.call(
|
||||
async move |instance| -> Result<axum::response::Response, WasmError> {
|
||||
let (mut parts, body) = req.into_parts();
|
||||
let bytes = body
|
||||
.collect()
|
||||
.await
|
||||
.map_err(|_err| WasmError::ChannelClosed)?
|
||||
.to_bytes();
|
||||
|
||||
parts.headers.insert(
|
||||
"__context",
|
||||
to_header_value(&HttpContext {
|
||||
kind: HttpContextKind::Http,
|
||||
registered_path: path.clone(),
|
||||
path_params: params
|
||||
.iter()
|
||||
.map(|(name, value)| (name.to_string(), value.to_string()))
|
||||
.collect(),
|
||||
user: user.map(|u| HttpContextUser {
|
||||
id: u.id,
|
||||
email: u.email,
|
||||
csrf_token: u.csrf_token,
|
||||
}),
|
||||
})?,
|
||||
);
|
||||
|
||||
let request = hyper::Request::from_parts(
|
||||
parts,
|
||||
BoxBody::new(http_body_util::Full::new(bytes).map_err(|_| unreachable!())),
|
||||
);
|
||||
|
||||
let response = instance.call_incoming_http_handler(request).await?;
|
||||
|
||||
let (parts, body) = response.into_parts();
|
||||
let bytes = body
|
||||
.collect()
|
||||
.await
|
||||
.map_err(|_err| WasmError::ChannelClosed)?
|
||||
.to_bytes();
|
||||
|
||||
return Ok(axum::response::Response::from_parts(parts, bytes.into()));
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
return match result {
|
||||
Ok(Ok(r)) => r,
|
||||
Ok(Err(err)) => internal_error_response(err),
|
||||
Err(err) => internal_error_response(err),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
router = router.route(
|
||||
path,
|
||||
match method {
|
||||
MethodType::Delete => axum::routing::delete(handler),
|
||||
MethodType::Get => axum::routing::get(handler),
|
||||
MethodType::Head => axum::routing::head(handler),
|
||||
MethodType::Options => axum::routing::options(handler),
|
||||
MethodType::Patch => axum::routing::patch(handler),
|
||||
MethodType::Post => axum::routing::post(handler),
|
||||
MethodType::Put => axum::routing::put(handler),
|
||||
MethodType::Trace => axum::routing::trace(handler),
|
||||
MethodType::Connect => axum::routing::connect(handler),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(Some(router));
|
||||
}
|
||||
|
||||
fn empty() -> BoxBody<Bytes, hyper::Error> {
|
||||
return BoxBody::new(http_body_util::Empty::new().map_err(|_| unreachable!()));
|
||||
}
|
||||
|
||||
fn to_header_value(context: &HttpContext) -> Result<hyper::http::HeaderValue, WasmError> {
|
||||
return hyper::http::HeaderValue::from_bytes(&serde_json::to_vec(&context).unwrap_or_default())
|
||||
.map_err(|_err| WasmError::Encoding);
|
||||
}
|
||||
|
||||
fn internal_error_response(err: impl std::string::ToString) -> axum::response::Response {
|
||||
return axum::response::Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(err.to_string().into())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
@@ -26,7 +26,7 @@ fn test_admin_permissions() {
|
||||
dev: false,
|
||||
disable_auth_ui: false,
|
||||
cors_allowed_origins: vec![],
|
||||
js_runtime_threads: None,
|
||||
runtime_threads: None,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -48,7 +48,7 @@ async fn test_record_apis() {
|
||||
dev: false,
|
||||
disable_auth_ui: false,
|
||||
cors_allowed_origins: vec![],
|
||||
js_runtime_threads: None,
|
||||
runtime_threads: None,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -32,7 +32,7 @@ fn test_https_serving() {
|
||||
dev: false,
|
||||
disable_auth_ui: false,
|
||||
cors_allowed_origins: vec![],
|
||||
js_runtime_threads: None,
|
||||
runtime_threads: None,
|
||||
tls_key: Some(tls_key),
|
||||
tls_cert: Some(cert.der().clone()),
|
||||
..Default::default()
|
||||
|
||||
@@ -26,7 +26,7 @@ sqlite3-parser = "0.15.0"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { workspace = true }
|
||||
trailbase-extension = { workspace = true }
|
||||
ts-rs = { version = "11", features = ["uuid-impl", "serde-json-impl"] }
|
||||
ts-rs = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
17
crates/wasi-keyvalue/Cargo.toml
Normal file
17
crates/wasi-keyvalue/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Fork of wasmtime/crates/wasi-keyvalue.
|
||||
[package]
|
||||
name = "trailbase-wasi-keyvalue"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "OSL-3.0"
|
||||
description = "Sync WASI KV store - fork of upstream"
|
||||
homepage = "https://trailbase.io"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.99"
|
||||
parking_lot = { workspace = true }
|
||||
wasmtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasmtime-wasi = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
233
crates/wasi-keyvalue/src/lib.rs
Normal file
233
crates/wasi-keyvalue/src/lib.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
//! # Wasmtime's [wasi-keyvalue] Implementation
|
||||
//!
|
||||
//! This crate provides a Wasmtime host implementation of the [wasi-keyvalue]
|
||||
//! API. With this crate, the runtime can run components that call APIs in
|
||||
//! [wasi-keyvalue] and provide components with access to key-value storages.
|
||||
//!
|
||||
//! Currently supported storage backends:
|
||||
//! * In-Memory (empty identifier)
|
||||
|
||||
#![allow(clippy::needless_return)]
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(clippy::unwrap_used)]
|
||||
#![warn(clippy::await_holding_lock, clippy::inefficient_to_string)]
|
||||
|
||||
mod generated {
|
||||
wasmtime::component::bindgen!({
|
||||
path: "wit",
|
||||
world: "wasi:keyvalue/imports",
|
||||
imports: { default: trappable },
|
||||
with: {
|
||||
"wasi:keyvalue/store/bucket": crate::Bucket,
|
||||
},
|
||||
trappable_error_type: {
|
||||
"wasi:keyvalue/store/error" => crate::Error,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
use self::generated::wasi::keyvalue;
|
||||
|
||||
use anyhow::Result;
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use wasmtime::component::{HasData, Resource, ResourceTable, ResourceTableError};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub enum Error {
|
||||
NoSuchStore,
|
||||
AccessDenied,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<ResourceTableError> for Error {
|
||||
fn from(err: ResourceTableError) -> Self {
|
||||
Self::Other(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
type InternalStore = Arc<RwLock<HashMap<String, Vec<u8>>>>;
|
||||
|
||||
/// The practical type for the inmemory Store.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Store {
|
||||
store: Arc<RwLock<HashMap<String, Vec<u8>>>>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// New shared storage for WASI KV implementation.
|
||||
pub fn new() -> Self {
|
||||
return Store {
|
||||
store: Arc::new(RwLock::new(HashMap::new())),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Bucket {
|
||||
in_memory_data: InternalStore,
|
||||
}
|
||||
|
||||
/// Capture the state necessary for use in the `wasi-keyvalue` API implementation.
|
||||
pub struct WasiKeyValueCtx {
|
||||
in_memory_data: InternalStore,
|
||||
}
|
||||
|
||||
impl WasiKeyValueCtx {
|
||||
/// Inject shared data.
|
||||
pub fn new(data: Store) -> Self {
|
||||
return Self {
|
||||
in_memory_data: data.store,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper capturing the needed internal `wasi-keyvalue` state.
|
||||
pub struct WasiKeyValue<'a> {
|
||||
ctx: &'a WasiKeyValueCtx,
|
||||
table: &'a mut ResourceTable,
|
||||
}
|
||||
|
||||
impl<'a> WasiKeyValue<'a> {
|
||||
/// Create a new view into the `wasi-keyvalue` state.
|
||||
pub fn new(ctx: &'a WasiKeyValueCtx, table: &'a mut ResourceTable) -> Self {
|
||||
Self { ctx, table }
|
||||
}
|
||||
}
|
||||
|
||||
impl keyvalue::store::Host for WasiKeyValue<'_> {
|
||||
fn open(&mut self, identifier: String) -> Result<Resource<Bucket>, Error> {
|
||||
match identifier.as_str() {
|
||||
"" => Ok(self.table.push(Bucket {
|
||||
in_memory_data: self.ctx.in_memory_data.clone(),
|
||||
})?),
|
||||
_ => Err(Error::NoSuchStore),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_error(&mut self, err: Error) -> Result<keyvalue::store::Error> {
|
||||
match err {
|
||||
Error::NoSuchStore => Ok(keyvalue::store::Error::NoSuchStore),
|
||||
Error::AccessDenied => Ok(keyvalue::store::Error::AccessDenied),
|
||||
Error::Other(e) => Ok(keyvalue::store::Error::Other(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl keyvalue::store::HostBucket for WasiKeyValue<'_> {
|
||||
fn get(&mut self, bucket: Resource<Bucket>, key: String) -> Result<Option<Vec<u8>>, Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
Ok(bucket.in_memory_data.read().get(&key).cloned())
|
||||
}
|
||||
|
||||
fn set(&mut self, bucket: Resource<Bucket>, key: String, value: Vec<u8>) -> Result<(), Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
bucket.in_memory_data.write().insert(key, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete(&mut self, bucket: Resource<Bucket>, key: String) -> Result<(), Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
bucket.in_memory_data.write().remove(&key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exists(&mut self, bucket: Resource<Bucket>, key: String) -> Result<bool, Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
Ok(bucket.in_memory_data.read().contains_key(&key))
|
||||
}
|
||||
|
||||
fn list_keys(
|
||||
&mut self,
|
||||
bucket: Resource<Bucket>,
|
||||
cursor: Option<u64>,
|
||||
) -> Result<keyvalue::store::KeyResponse, Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
let keys: Vec<String> = bucket.in_memory_data.read().keys().cloned().collect();
|
||||
let cursor = cursor.unwrap_or(0) as usize;
|
||||
let keys_slice = &keys[cursor..];
|
||||
Ok(keyvalue::store::KeyResponse {
|
||||
keys: keys_slice.to_vec(),
|
||||
cursor: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn drop(&mut self, bucket: Resource<Bucket>) -> Result<()> {
|
||||
self.table.delete(bucket)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl keyvalue::atomics::Host for WasiKeyValue<'_> {
|
||||
fn increment(&mut self, bucket: Resource<Bucket>, key: String, delta: u64) -> Result<u64, Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
let mut data = bucket.in_memory_data.write();
|
||||
let value = data.entry(key.clone()).or_insert(b"0".to_vec());
|
||||
|
||||
let current_value = String::from_utf8(value.clone())
|
||||
.map_err(|e| Error::Other(e.to_string()))?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
let new_value = current_value + delta;
|
||||
|
||||
*value = new_value.to_string().into_bytes();
|
||||
Ok(new_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl keyvalue::batch::Host for WasiKeyValue<'_> {
|
||||
fn get_many(
|
||||
&mut self,
|
||||
bucket: Resource<Bucket>,
|
||||
keys: Vec<String>,
|
||||
) -> Result<Vec<Option<(String, Vec<u8>)>>, Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
let lock = bucket.in_memory_data.read();
|
||||
Ok(
|
||||
keys
|
||||
.into_iter()
|
||||
.map(|key| lock.get(&key).map(|value| (key.clone(), value.clone())))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn set_many(
|
||||
&mut self,
|
||||
bucket: Resource<Bucket>,
|
||||
key_values: Vec<(String, Vec<u8>)>,
|
||||
) -> Result<(), Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
let mut lock = bucket.in_memory_data.write();
|
||||
for (key, value) in key_values {
|
||||
lock.insert(key, value);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_many(&mut self, bucket: Resource<Bucket>, keys: Vec<String>) -> Result<(), Error> {
|
||||
let bucket = self.table.get_mut(&bucket)?;
|
||||
let mut lock = bucket.in_memory_data.write();
|
||||
for key in keys {
|
||||
lock.remove(&key);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add all the `wasi-keyvalue` world's interfaces to a [`wasmtime::component::Linker`].
|
||||
pub fn add_to_linker<T: Send + 'static>(
|
||||
l: &mut wasmtime::component::Linker<T>,
|
||||
f: fn(&mut T) -> WasiKeyValue<'_>,
|
||||
) -> Result<()> {
|
||||
keyvalue::store::add_to_linker::<_, HasWasiKeyValue>(l, f)?;
|
||||
keyvalue::atomics::add_to_linker::<_, HasWasiKeyValue>(l, f)?;
|
||||
keyvalue::batch::add_to_linker::<_, HasWasiKeyValue>(l, f)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct HasWasiKeyValue;
|
||||
|
||||
impl HasData for HasWasiKeyValue {
|
||||
type Data<'a> = WasiKeyValue<'a>;
|
||||
}
|
||||
22
crates/wasi-keyvalue/wit/deps/keyvalue/atomic.wit
Normal file
22
crates/wasi-keyvalue/wit/deps/keyvalue/atomic.wit
Normal file
@@ -0,0 +1,22 @@
|
||||
/// A keyvalue interface that provides atomic operations.
|
||||
///
|
||||
/// Atomic operations are single, indivisible operations. When a fault causes an atomic operation to
|
||||
/// fail, it will appear to the invoker of the atomic operation that the action either completed
|
||||
/// successfully or did nothing at all.
|
||||
///
|
||||
/// Please note that this interface is bare functions that take a reference to a bucket. This is to
|
||||
/// get around the current lack of a way to "extend" a resource with additional methods inside of
|
||||
/// wit. Future version of the interface will instead extend these methods on the base `bucket`
|
||||
/// resource.
|
||||
interface atomics {
|
||||
use store.{bucket, error};
|
||||
|
||||
/// Atomically increment the value associated with the key in the store by the given delta. It
|
||||
/// returns the new value.
|
||||
///
|
||||
/// If the key does not exist in the store, it creates a new key-value pair with the value set
|
||||
/// to the given delta.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
increment: func(bucket: borrow<bucket>, key: string, delta: u64) -> result<u64, error>;
|
||||
}
|
||||
63
crates/wasi-keyvalue/wit/deps/keyvalue/batch.wit
Normal file
63
crates/wasi-keyvalue/wit/deps/keyvalue/batch.wit
Normal file
@@ -0,0 +1,63 @@
|
||||
/// A keyvalue interface that provides batch operations.
|
||||
///
|
||||
/// A batch operation is an operation that operates on multiple keys at once.
|
||||
///
|
||||
/// Batch operations are useful for reducing network round-trip time. For example, if you want to
|
||||
/// get the values associated with 100 keys, you can either do 100 get operations or you can do 1
|
||||
/// batch get operation. The batch operation is faster because it only needs to make 1 network call
|
||||
/// instead of 100.
|
||||
///
|
||||
/// A batch operation does not guarantee atomicity, meaning that if the batch operation fails, some
|
||||
/// of the keys may have been modified and some may not.
|
||||
///
|
||||
/// This interface does has the same consistency guarantees as the `store` interface, meaning that
|
||||
/// you should be able to "read your writes."
|
||||
///
|
||||
/// Please note that this interface is bare functions that take a reference to a bucket. This is to
|
||||
/// get around the current lack of a way to "extend" a resource with additional methods inside of
|
||||
/// wit. Future version of the interface will instead extend these methods on the base `bucket`
|
||||
/// resource.
|
||||
interface batch {
|
||||
use store.{bucket, error};
|
||||
|
||||
/// Get the key-value pairs associated with the keys in the store. It returns a list of
|
||||
/// key-value pairs.
|
||||
///
|
||||
/// If any of the keys do not exist in the store, it returns a `none` value for that pair in the
|
||||
/// list.
|
||||
///
|
||||
/// MAY show an out-of-date value if there are concurrent writes to the store.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
get-many: func(bucket: borrow<bucket>, keys: list<string>) -> result<list<option<tuple<string, list<u8>>>>, error>;
|
||||
|
||||
/// Set the values associated with the keys in the store. If the key already exists in the
|
||||
/// store, it overwrites the value.
|
||||
///
|
||||
/// Note that the key-value pairs are not guaranteed to be set in the order they are provided.
|
||||
///
|
||||
/// If any of the keys do not exist in the store, it creates a new key-value pair.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not
|
||||
/// rollback the key-value pairs that were already set. Thus, this batch operation does not
|
||||
/// guarantee atomicity, implying that some key-value pairs could be set while others might
|
||||
/// fail.
|
||||
///
|
||||
/// Other concurrent operations may also be able to see the partial results.
|
||||
set-many: func(bucket: borrow<bucket>, key-values: list<tuple<string, list<u8>>>) -> result<_, error>;
|
||||
|
||||
/// Delete the key-value pairs associated with the keys in the store.
|
||||
///
|
||||
/// Note that the key-value pairs are not guaranteed to be deleted in the order they are
|
||||
/// provided.
|
||||
///
|
||||
/// If any of the keys do not exist in the store, it skips the key.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not
|
||||
/// rollback the key-value pairs that were already deleted. Thus, this batch operation does not
|
||||
/// guarantee atomicity, implying that some key-value pairs could be deleted while others might
|
||||
/// fail.
|
||||
///
|
||||
/// Other concurrent operations may also be able to see the partial results.
|
||||
delete-many: func(bucket: borrow<bucket>, keys: list<string>) -> result<_, error>;
|
||||
}
|
||||
122
crates/wasi-keyvalue/wit/deps/keyvalue/store.wit
Normal file
122
crates/wasi-keyvalue/wit/deps/keyvalue/store.wit
Normal file
@@ -0,0 +1,122 @@
|
||||
/// A keyvalue interface that provides eventually consistent key-value operations.
|
||||
///
|
||||
/// Each of these operations acts on a single key-value pair.
|
||||
///
|
||||
/// The value in the key-value pair is defined as a `u8` byte array and the intention is that it is
|
||||
/// the common denominator for all data types defined by different key-value stores to handle data,
|
||||
/// ensuring compatibility between different key-value stores. Note: the clients will be expecting
|
||||
/// serialization/deserialization overhead to be handled by the key-value store. The value could be
|
||||
/// a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects.
|
||||
///
|
||||
/// Data consistency in a key value store refers to the guarantee that once a write operation
|
||||
/// completes, all subsequent read operations will return the value that was written.
|
||||
///
|
||||
/// Any implementation of this interface must have enough consistency to guarantee "reading your
|
||||
/// writes." In particular, this means that the client should never get a value that is older than
|
||||
/// the one it wrote, but it MAY get a newer value if one was written around the same time. These
|
||||
/// guarantees only apply to the same client (which will likely be provided by the host or an
|
||||
/// external capability of some kind). In this context a "client" is referring to the caller or
|
||||
/// guest that is consuming this interface. Once a write request is committed by a specific client,
|
||||
/// all subsequent read requests by the same client will reflect that write or any subsequent
|
||||
/// writes. Another client running in a different context may or may not immediately see the result
|
||||
/// due to the replication lag. As an example of all of this, if a value at a given key is A, and
|
||||
/// the client writes B, then immediately reads, it should get B. If something else writes C in
|
||||
/// quick succession, then the client may get C. However, a client running in a separate context may
|
||||
/// still see A or B
|
||||
interface store {
|
||||
/// The set of errors which may be raised by functions in this package
|
||||
variant error {
|
||||
/// The host does not recognize the store identifier requested.
|
||||
no-such-store,
|
||||
|
||||
/// The requesting component does not have access to the specified store
|
||||
/// (which may or may not exist).
|
||||
access-denied,
|
||||
|
||||
/// Some implementation-specific error has occurred (e.g. I/O)
|
||||
other(string)
|
||||
}
|
||||
|
||||
/// A response to a `list-keys` operation.
|
||||
record key-response {
|
||||
/// The list of keys returned by the query.
|
||||
keys: list<string>,
|
||||
/// The continuation token to use to fetch the next page of keys. If this is `null`, then
|
||||
/// there are no more keys to fetch.
|
||||
cursor: option<u64>
|
||||
}
|
||||
|
||||
/// Get the bucket with the specified identifier.
|
||||
///
|
||||
/// `identifier` must refer to a bucket provided by the host.
|
||||
///
|
||||
/// `error::no-such-store` will be raised if the `identifier` is not recognized.
|
||||
open: func(identifier: string) -> result<bucket, error>;
|
||||
|
||||
/// A bucket is a collection of key-value pairs. Each key-value pair is stored as a entry in the
|
||||
/// bucket, and the bucket itself acts as a collection of all these entries.
|
||||
///
|
||||
/// It is worth noting that the exact terminology for bucket in key-value stores can very
|
||||
/// depending on the specific implementation. For example:
|
||||
///
|
||||
/// 1. Amazon DynamoDB calls a collection of key-value pairs a table
|
||||
/// 2. Redis has hashes, sets, and sorted sets as different types of collections
|
||||
/// 3. Cassandra calls a collection of key-value pairs a column family
|
||||
/// 4. MongoDB calls a collection of key-value pairs a collection
|
||||
/// 5. Riak calls a collection of key-value pairs a bucket
|
||||
/// 6. Memcached calls a collection of key-value pairs a slab
|
||||
/// 7. Azure Cosmos DB calls a collection of key-value pairs a container
|
||||
///
|
||||
/// In this interface, we use the term `bucket` to refer to a collection of key-value pairs
|
||||
resource bucket {
|
||||
/// Get the value associated with the specified `key`
|
||||
///
|
||||
/// The value is returned as an option. If the key-value pair exists in the
|
||||
/// store, it returns `Ok(value)`. If the key does not exist in the
|
||||
/// store, it returns `Ok(none)`.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
get: func(key: string) -> result<option<list<u8>>, error>;
|
||||
|
||||
/// Set the value associated with the key in the store. If the key already
|
||||
/// exists in the store, it overwrites the value.
|
||||
///
|
||||
/// If the key does not exist in the store, it creates a new key-value pair.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
set: func(key: string, value: list<u8>) -> result<_, error>;
|
||||
|
||||
/// Delete the key-value pair associated with the key in the store.
|
||||
///
|
||||
/// If the key does not exist in the store, it does nothing.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
delete: func(key: string) -> result<_, error>;
|
||||
|
||||
/// Check if the key exists in the store.
|
||||
///
|
||||
/// If the key exists in the store, it returns `Ok(true)`. If the key does
|
||||
/// not exist in the store, it returns `Ok(false)`.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
exists: func(key: string) -> result<bool, error>;
|
||||
|
||||
/// Get all the keys in the store with an optional cursor (for use in pagination). It
|
||||
/// returns a list of keys. Please note that for most KeyValue implementations, this is a
|
||||
/// can be a very expensive operation and so it should be used judiciously. Implementations
|
||||
/// can return any number of keys in a single response, but they should never attempt to
|
||||
/// send more data than is reasonable (i.e. on a small edge device, this may only be a few
|
||||
/// KB, while on a large machine this could be several MB). Any response should also return
|
||||
/// a cursor that can be used to fetch the next page of keys. See the `key-response` record
|
||||
/// for more information.
|
||||
///
|
||||
/// Note that the keys are not guaranteed to be returned in any particular order.
|
||||
///
|
||||
/// If the store is empty, it returns an empty list.
|
||||
///
|
||||
/// MAY show an out-of-date list of keys if there are concurrent writes to the store.
|
||||
///
|
||||
/// If any error occurs, it returns an `Err(error)`.
|
||||
list-keys: func(cursor: option<u64>) -> result<key-response, error>;
|
||||
}
|
||||
}
|
||||
16
crates/wasi-keyvalue/wit/deps/keyvalue/watch.wit
Normal file
16
crates/wasi-keyvalue/wit/deps/keyvalue/watch.wit
Normal file
@@ -0,0 +1,16 @@
|
||||
/// A keyvalue interface that provides watch operations.
|
||||
///
|
||||
/// This interface is used to provide event-driven mechanisms to handle
|
||||
/// keyvalue changes.
|
||||
interface watcher {
|
||||
/// A keyvalue interface that provides handle-watch operations.
|
||||
use store.{bucket};
|
||||
|
||||
/// Handle the `set` event for the given bucket and key. It includes a reference to the `bucket`
|
||||
/// that can be used to interact with the store.
|
||||
on-set: func(bucket: bucket, key: string, value: list<u8>);
|
||||
|
||||
/// Handle the `delete` event for the given bucket and key. It includes a reference to the
|
||||
/// `bucket` that can be used to interact with the store.
|
||||
on-delete: func(bucket: bucket, key: string);
|
||||
}
|
||||
26
crates/wasi-keyvalue/wit/deps/keyvalue/world.wit
Normal file
26
crates/wasi-keyvalue/wit/deps/keyvalue/world.wit
Normal file
@@ -0,0 +1,26 @@
|
||||
package wasi:keyvalue@0.2.0-draft;
|
||||
|
||||
/// The `wasi:keyvalue/imports` world provides common APIs for interacting with key-value stores.
|
||||
/// Components targeting this world will be able to do:
|
||||
///
|
||||
/// 1. CRUD (create, read, update, delete) operations on key-value stores.
|
||||
/// 2. Atomic `increment` and CAS (compare-and-swap) operations.
|
||||
/// 3. Batch operations that can reduce the number of round trips to the network.
|
||||
world imports {
|
||||
/// The `store` capability allows the component to perform eventually consistent operations on
|
||||
/// the key-value store.
|
||||
import store;
|
||||
|
||||
/// The `atomic` capability allows the component to perform atomic / `increment` and CAS
|
||||
/// (compare-and-swap) operations.
|
||||
import atomics;
|
||||
|
||||
/// The `batch` capability allows the component to perform eventually consistent batch
|
||||
/// operations that can reduce the number of round trips to the network.
|
||||
import batch;
|
||||
}
|
||||
|
||||
world watch-service {
|
||||
include imports;
|
||||
export watcher;
|
||||
}
|
||||
6
crates/wasi-keyvalue/wit/world.wit
Normal file
6
crates/wasi-keyvalue/wit/world.wit
Normal file
@@ -0,0 +1,6 @@
|
||||
// We actually don't use this; it's just to let bindgen! find the corresponding world in wit/deps.
|
||||
package wasmtime:wasi-keyvalue;
|
||||
|
||||
world bindings {
|
||||
include wasi:keyvalue/imports@0.2.0-draft;
|
||||
}
|
||||
16
crates/wasm-runtime-common/Cargo.toml
Normal file
16
crates/wasm-runtime-common/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "trailbase-wasm-common"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "OSL-3.0"
|
||||
description = "WASM runtime for the TrailBase framework"
|
||||
homepage = "https://trailbase.io"
|
||||
exclude = [
|
||||
"**/node_modules/",
|
||||
"**/dist/",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "^1.0.203", features = ["derive"] }
|
||||
serde_json = "^1.0.117"
|
||||
ts-rs = { workspace = true }
|
||||
51
crates/wasm-runtime-common/src/lib.rs
Normal file
51
crates/wasm-runtime-common/src/lib.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
#![forbid(unsafe_code, clippy::unwrap_used)]
|
||||
#![allow(clippy::needless_return)]
|
||||
#![warn(clippy::await_holding_lock, clippy::inefficient_to_string)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct SqliteRequest {
|
||||
pub query: String,
|
||||
pub params: Vec<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum SqliteResponse {
|
||||
Query { rows: Vec<Vec<serde_json::Value>> },
|
||||
Execute { rows_affected: usize },
|
||||
Error(String),
|
||||
TxBegin,
|
||||
TxCommit,
|
||||
TxRollback,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
pub enum HttpContextKind {
|
||||
/// An incoming http request.
|
||||
Http,
|
||||
/// An incoming job request.
|
||||
Job,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
pub struct HttpContextUser {
|
||||
/// Url-safe Base64 encoded id of the current user.
|
||||
pub id: String,
|
||||
/// E-mail of the current user.
|
||||
pub email: String,
|
||||
/// The "expected" CSRF token as included in the auth token claims [User] was constructed from.
|
||||
pub csrf_token: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct HttpContext {
|
||||
pub kind: HttpContextKind,
|
||||
pub registered_path: String,
|
||||
pub path_params: Vec<(String, String)>,
|
||||
pub user: Option<HttpContextUser>,
|
||||
}
|
||||
10
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/command.wit
Normal file
10
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/command.wit
Normal file
@@ -0,0 +1,10 @@
|
||||
package wasi:cli@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
world command {
|
||||
@since(version = 0.2.0)
|
||||
include imports;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
export run;
|
||||
}
|
||||
22
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/environment.wit
Normal file
22
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/environment.wit
Normal file
@@ -0,0 +1,22 @@
|
||||
@since(version = 0.2.0)
|
||||
interface environment {
|
||||
/// Get the POSIX-style environment variables.
|
||||
///
|
||||
/// Each environment variable is provided as a pair of string variable names
|
||||
/// and string value.
|
||||
///
|
||||
/// Morally, these are a value import, but until value imports are available
|
||||
/// in the component model, this import function should return the same
|
||||
/// values each time it is called.
|
||||
@since(version = 0.2.0)
|
||||
get-environment: func() -> list<tuple<string, string>>;
|
||||
|
||||
/// Get the POSIX-style arguments to the program.
|
||||
@since(version = 0.2.0)
|
||||
get-arguments: func() -> list<string>;
|
||||
|
||||
/// Return a path that programs should use as their initial current working
|
||||
/// directory, interpreting `.` as shorthand for this.
|
||||
@since(version = 0.2.0)
|
||||
initial-cwd: func() -> option<string>;
|
||||
}
|
||||
17
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/exit.wit
Normal file
17
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/exit.wit
Normal file
@@ -0,0 +1,17 @@
|
||||
@since(version = 0.2.0)
|
||||
interface exit {
|
||||
/// Exit the current instance and any linked instances.
|
||||
@since(version = 0.2.0)
|
||||
exit: func(status: result);
|
||||
|
||||
/// Exit the current instance and any linked instances, reporting the
|
||||
/// specified status code to the host.
|
||||
///
|
||||
/// The meaning of the code depends on the context, with 0 usually meaning
|
||||
/// "success", and other values indicating various types of failure.
|
||||
///
|
||||
/// This function does not return; the effect is analogous to a trap, but
|
||||
/// without the connotation that something bad has happened.
|
||||
@unstable(feature = cli-exit-with-code)
|
||||
exit-with-code: func(status-code: u8);
|
||||
}
|
||||
36
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/imports.wit
Normal file
36
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/imports.wit
Normal file
@@ -0,0 +1,36 @@
|
||||
package wasi:cli@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
world imports {
|
||||
@since(version = 0.2.0)
|
||||
include wasi:clocks/imports@0.2.6;
|
||||
@since(version = 0.2.0)
|
||||
include wasi:filesystem/imports@0.2.6;
|
||||
@since(version = 0.2.0)
|
||||
include wasi:sockets/imports@0.2.6;
|
||||
@since(version = 0.2.0)
|
||||
include wasi:random/imports@0.2.6;
|
||||
@since(version = 0.2.0)
|
||||
include wasi:io/imports@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
import environment;
|
||||
@since(version = 0.2.0)
|
||||
import exit;
|
||||
@since(version = 0.2.0)
|
||||
import stdin;
|
||||
@since(version = 0.2.0)
|
||||
import stdout;
|
||||
@since(version = 0.2.0)
|
||||
import stderr;
|
||||
@since(version = 0.2.0)
|
||||
import terminal-input;
|
||||
@since(version = 0.2.0)
|
||||
import terminal-output;
|
||||
@since(version = 0.2.0)
|
||||
import terminal-stdin;
|
||||
@since(version = 0.2.0)
|
||||
import terminal-stdout;
|
||||
@since(version = 0.2.0)
|
||||
import terminal-stderr;
|
||||
}
|
||||
6
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/run.wit
Normal file
6
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/run.wit
Normal file
@@ -0,0 +1,6 @@
|
||||
@since(version = 0.2.0)
|
||||
interface run {
|
||||
/// Run the program.
|
||||
@since(version = 0.2.0)
|
||||
run: func() -> result;
|
||||
}
|
||||
26
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/stdio.wit
Normal file
26
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/stdio.wit
Normal file
@@ -0,0 +1,26 @@
|
||||
@since(version = 0.2.0)
|
||||
interface stdin {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/streams@0.2.6.{input-stream};
|
||||
|
||||
@since(version = 0.2.0)
|
||||
get-stdin: func() -> input-stream;
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
interface stdout {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/streams@0.2.6.{output-stream};
|
||||
|
||||
@since(version = 0.2.0)
|
||||
get-stdout: func() -> output-stream;
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
interface stderr {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/streams@0.2.6.{output-stream};
|
||||
|
||||
@since(version = 0.2.0)
|
||||
get-stderr: func() -> output-stream;
|
||||
}
|
||||
62
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/terminal.wit
Normal file
62
crates/wasm-runtime-guest/wit/deps-0.2.6/cli/terminal.wit
Normal file
@@ -0,0 +1,62 @@
|
||||
/// Terminal input.
|
||||
///
|
||||
/// In the future, this may include functions for disabling echoing,
|
||||
/// disabling input buffering so that keyboard events are sent through
|
||||
/// immediately, querying supported features, and so on.
|
||||
@since(version = 0.2.0)
|
||||
interface terminal-input {
|
||||
/// The input side of a terminal.
|
||||
@since(version = 0.2.0)
|
||||
resource terminal-input;
|
||||
}
|
||||
|
||||
/// Terminal output.
|
||||
///
|
||||
/// In the future, this may include functions for querying the terminal
|
||||
/// size, being notified of terminal size changes, querying supported
|
||||
/// features, and so on.
|
||||
@since(version = 0.2.0)
|
||||
interface terminal-output {
|
||||
/// The output side of a terminal.
|
||||
@since(version = 0.2.0)
|
||||
resource terminal-output;
|
||||
}
|
||||
|
||||
/// An interface providing an optional `terminal-input` for stdin as a
|
||||
/// link-time authority.
|
||||
@since(version = 0.2.0)
|
||||
interface terminal-stdin {
|
||||
@since(version = 0.2.0)
|
||||
use terminal-input.{terminal-input};
|
||||
|
||||
/// If stdin is connected to a terminal, return a `terminal-input` handle
|
||||
/// allowing further interaction with it.
|
||||
@since(version = 0.2.0)
|
||||
get-terminal-stdin: func() -> option<terminal-input>;
|
||||
}
|
||||
|
||||
/// An interface providing an optional `terminal-output` for stdout as a
|
||||
/// link-time authority.
|
||||
@since(version = 0.2.0)
|
||||
interface terminal-stdout {
|
||||
@since(version = 0.2.0)
|
||||
use terminal-output.{terminal-output};
|
||||
|
||||
/// If stdout is connected to a terminal, return a `terminal-output` handle
|
||||
/// allowing further interaction with it.
|
||||
@since(version = 0.2.0)
|
||||
get-terminal-stdout: func() -> option<terminal-output>;
|
||||
}
|
||||
|
||||
/// An interface providing an optional `terminal-output` for stderr as a
|
||||
/// link-time authority.
|
||||
@since(version = 0.2.0)
|
||||
interface terminal-stderr {
|
||||
@since(version = 0.2.0)
|
||||
use terminal-output.{terminal-output};
|
||||
|
||||
/// If stderr is connected to a terminal, return a `terminal-output` handle
|
||||
/// allowing further interaction with it.
|
||||
@since(version = 0.2.0)
|
||||
get-terminal-stderr: func() -> option<terminal-output>;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package wasi:clocks@0.2.6;
|
||||
/// WASI Monotonic Clock is a clock API intended to let users measure elapsed
|
||||
/// time.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
///
|
||||
/// A monotonic clock is a clock which has an unspecified initial value, and
|
||||
/// successive reads of the clock will produce non-decreasing values.
|
||||
@since(version = 0.2.0)
|
||||
interface monotonic-clock {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/poll@0.2.6.{pollable};
|
||||
|
||||
/// An instant in time, in nanoseconds. An instant is relative to an
|
||||
/// unspecified initial value, and can only be compared to instances from
|
||||
/// the same monotonic-clock.
|
||||
@since(version = 0.2.0)
|
||||
type instant = u64;
|
||||
|
||||
/// A duration of time, in nanoseconds.
|
||||
@since(version = 0.2.0)
|
||||
type duration = u64;
|
||||
|
||||
/// Read the current value of the clock.
|
||||
///
|
||||
/// The clock is monotonic, therefore calling this function repeatedly will
|
||||
/// produce a sequence of non-decreasing values.
|
||||
@since(version = 0.2.0)
|
||||
now: func() -> instant;
|
||||
|
||||
/// Query the resolution of the clock. Returns the duration of time
|
||||
/// corresponding to a clock tick.
|
||||
@since(version = 0.2.0)
|
||||
resolution: func() -> duration;
|
||||
|
||||
/// Create a `pollable` which will resolve once the specified instant
|
||||
/// has occurred.
|
||||
@since(version = 0.2.0)
|
||||
subscribe-instant: func(
|
||||
when: instant,
|
||||
) -> pollable;
|
||||
|
||||
/// Create a `pollable` that will resolve after the specified duration has
|
||||
/// elapsed from the time this function is invoked.
|
||||
@since(version = 0.2.0)
|
||||
subscribe-duration: func(
|
||||
when: duration,
|
||||
) -> pollable;
|
||||
}
|
||||
55
crates/wasm-runtime-guest/wit/deps-0.2.6/clocks/timezone.wit
Normal file
55
crates/wasm-runtime-guest/wit/deps-0.2.6/clocks/timezone.wit
Normal file
@@ -0,0 +1,55 @@
|
||||
package wasi:clocks@0.2.6;
|
||||
|
||||
@unstable(feature = clocks-timezone)
|
||||
interface timezone {
|
||||
@unstable(feature = clocks-timezone)
|
||||
use wall-clock.{datetime};
|
||||
|
||||
/// Return information needed to display the given `datetime`. This includes
|
||||
/// the UTC offset, the time zone name, and a flag indicating whether
|
||||
/// daylight saving time is active.
|
||||
///
|
||||
/// If the timezone cannot be determined for the given `datetime`, return a
|
||||
/// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight
|
||||
/// saving time.
|
||||
@unstable(feature = clocks-timezone)
|
||||
display: func(when: datetime) -> timezone-display;
|
||||
|
||||
/// The same as `display`, but only return the UTC offset.
|
||||
@unstable(feature = clocks-timezone)
|
||||
utc-offset: func(when: datetime) -> s32;
|
||||
|
||||
/// Information useful for displaying the timezone of a specific `datetime`.
|
||||
///
|
||||
/// This information may vary within a single `timezone` to reflect daylight
|
||||
/// saving time adjustments.
|
||||
@unstable(feature = clocks-timezone)
|
||||
record timezone-display {
|
||||
/// The number of seconds difference between UTC time and the local
|
||||
/// time of the timezone.
|
||||
///
|
||||
/// The returned value will always be less than 86400 which is the
|
||||
/// number of seconds in a day (24*60*60).
|
||||
///
|
||||
/// In implementations that do not expose an actual time zone, this
|
||||
/// should return 0.
|
||||
utc-offset: s32,
|
||||
|
||||
/// The abbreviated name of the timezone to display to a user. The name
|
||||
/// `UTC` indicates Coordinated Universal Time. Otherwise, this should
|
||||
/// reference local standards for the name of the time zone.
|
||||
///
|
||||
/// In implementations that do not expose an actual time zone, this
|
||||
/// should be the string `UTC`.
|
||||
///
|
||||
/// In time zones that do not have an applicable name, a formatted
|
||||
/// representation of the UTC offset may be returned, such as `-04:00`.
|
||||
name: string,
|
||||
|
||||
/// Whether daylight saving time is active.
|
||||
///
|
||||
/// In implementations that do not expose an actual time zone, this
|
||||
/// should return false.
|
||||
in-daylight-saving-time: bool,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package wasi:clocks@0.2.6;
|
||||
/// WASI Wall Clock is a clock API intended to let users query the current
|
||||
/// time. The name "wall" makes an analogy to a "clock on the wall", which
|
||||
/// is not necessarily monotonic as it may be reset.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
///
|
||||
/// A wall clock is a clock which measures the date and time according to
|
||||
/// some external reference.
|
||||
///
|
||||
/// External references may be reset, so this clock is not necessarily
|
||||
/// monotonic, making it unsuitable for measuring elapsed time.
|
||||
///
|
||||
/// It is intended for reporting the current date and time for humans.
|
||||
@since(version = 0.2.0)
|
||||
interface wall-clock {
|
||||
/// A time and date in seconds plus nanoseconds.
|
||||
@since(version = 0.2.0)
|
||||
record datetime {
|
||||
seconds: u64,
|
||||
nanoseconds: u32,
|
||||
}
|
||||
|
||||
/// Read the current value of the clock.
|
||||
///
|
||||
/// This clock is not monotonic, therefore calling this function repeatedly
|
||||
/// will not necessarily produce a sequence of non-decreasing values.
|
||||
///
|
||||
/// The returned timestamps represent the number of seconds since
|
||||
/// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch],
|
||||
/// also known as [Unix Time].
|
||||
///
|
||||
/// The nanoseconds field of the output is always less than 1000000000.
|
||||
///
|
||||
/// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16
|
||||
/// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time
|
||||
@since(version = 0.2.0)
|
||||
now: func() -> datetime;
|
||||
|
||||
/// Query the resolution of the clock.
|
||||
///
|
||||
/// The nanoseconds field of the output is always less than 1000000000.
|
||||
@since(version = 0.2.0)
|
||||
resolution: func() -> datetime;
|
||||
}
|
||||
11
crates/wasm-runtime-guest/wit/deps-0.2.6/clocks/world.wit
Normal file
11
crates/wasm-runtime-guest/wit/deps-0.2.6/clocks/world.wit
Normal file
@@ -0,0 +1,11 @@
|
||||
package wasi:clocks@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
world imports {
|
||||
@since(version = 0.2.0)
|
||||
import monotonic-clock;
|
||||
@since(version = 0.2.0)
|
||||
import wall-clock;
|
||||
@unstable(feature = clocks-timezone)
|
||||
import timezone;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package wasi:filesystem@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
interface preopens {
|
||||
@since(version = 0.2.0)
|
||||
use types.{descriptor};
|
||||
|
||||
/// Return the set of preopened directories, and their paths.
|
||||
@since(version = 0.2.0)
|
||||
get-directories: func() -> list<tuple<descriptor, string>>;
|
||||
}
|
||||
676
crates/wasm-runtime-guest/wit/deps-0.2.6/filesystem/types.wit
Normal file
676
crates/wasm-runtime-guest/wit/deps-0.2.6/filesystem/types.wit
Normal file
@@ -0,0 +1,676 @@
|
||||
package wasi:filesystem@0.2.6;
|
||||
/// WASI filesystem is a filesystem API primarily intended to let users run WASI
|
||||
/// programs that access their files on their existing filesystems, without
|
||||
/// significant overhead.
|
||||
///
|
||||
/// It is intended to be roughly portable between Unix-family platforms and
|
||||
/// Windows, though it does not hide many of the major differences.
|
||||
///
|
||||
/// Paths are passed as interface-type `string`s, meaning they must consist of
|
||||
/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain
|
||||
/// paths which are not accessible by this API.
|
||||
///
|
||||
/// The directory separator in WASI is always the forward-slash (`/`).
|
||||
///
|
||||
/// All paths in WASI are relative paths, and are interpreted relative to a
|
||||
/// `descriptor` referring to a base directory. If a `path` argument to any WASI
|
||||
/// function starts with `/`, or if any step of resolving a `path`, including
|
||||
/// `..` and symbolic link steps, reaches a directory outside of the base
|
||||
/// directory, or reaches a symlink to an absolute or rooted path in the
|
||||
/// underlying filesystem, the function fails with `error-code::not-permitted`.
|
||||
///
|
||||
/// For more information about WASI path resolution and sandboxing, see
|
||||
/// [WASI filesystem path resolution].
|
||||
///
|
||||
/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md
|
||||
@since(version = 0.2.0)
|
||||
interface types {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/streams@0.2.6.{input-stream, output-stream, error};
|
||||
@since(version = 0.2.0)
|
||||
use wasi:clocks/wall-clock@0.2.6.{datetime};
|
||||
|
||||
/// File size or length of a region within a file.
|
||||
@since(version = 0.2.0)
|
||||
type filesize = u64;
|
||||
|
||||
/// The type of a filesystem object referenced by a descriptor.
|
||||
///
|
||||
/// Note: This was called `filetype` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
enum descriptor-type {
|
||||
/// The type of the descriptor or file is unknown or is different from
|
||||
/// any of the other types specified.
|
||||
unknown,
|
||||
/// The descriptor refers to a block device inode.
|
||||
block-device,
|
||||
/// The descriptor refers to a character device inode.
|
||||
character-device,
|
||||
/// The descriptor refers to a directory inode.
|
||||
directory,
|
||||
/// The descriptor refers to a named pipe.
|
||||
fifo,
|
||||
/// The file refers to a symbolic link inode.
|
||||
symbolic-link,
|
||||
/// The descriptor refers to a regular file inode.
|
||||
regular-file,
|
||||
/// The descriptor refers to a socket.
|
||||
socket,
|
||||
}
|
||||
|
||||
/// Descriptor flags.
|
||||
///
|
||||
/// Note: This was called `fdflags` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
flags descriptor-flags {
|
||||
/// Read mode: Data can be read.
|
||||
read,
|
||||
/// Write mode: Data can be written to.
|
||||
write,
|
||||
/// Request that writes be performed according to synchronized I/O file
|
||||
/// integrity completion. The data stored in the file and the file's
|
||||
/// metadata are synchronized. This is similar to `O_SYNC` in POSIX.
|
||||
///
|
||||
/// The precise semantics of this operation have not yet been defined for
|
||||
/// WASI. At this time, it should be interpreted as a request, and not a
|
||||
/// requirement.
|
||||
file-integrity-sync,
|
||||
/// Request that writes be performed according to synchronized I/O data
|
||||
/// integrity completion. Only the data stored in the file is
|
||||
/// synchronized. This is similar to `O_DSYNC` in POSIX.
|
||||
///
|
||||
/// The precise semantics of this operation have not yet been defined for
|
||||
/// WASI. At this time, it should be interpreted as a request, and not a
|
||||
/// requirement.
|
||||
data-integrity-sync,
|
||||
/// Requests that reads be performed at the same level of integrity
|
||||
/// requested for writes. This is similar to `O_RSYNC` in POSIX.
|
||||
///
|
||||
/// The precise semantics of this operation have not yet been defined for
|
||||
/// WASI. At this time, it should be interpreted as a request, and not a
|
||||
/// requirement.
|
||||
requested-write-sync,
|
||||
/// Mutating directories mode: Directory contents may be mutated.
|
||||
///
|
||||
/// When this flag is unset on a descriptor, operations using the
|
||||
/// descriptor which would create, rename, delete, modify the data or
|
||||
/// metadata of filesystem objects, or obtain another handle which
|
||||
/// would permit any of those, shall fail with `error-code::read-only` if
|
||||
/// they would otherwise succeed.
|
||||
///
|
||||
/// This may only be set on directories.
|
||||
mutate-directory,
|
||||
}
|
||||
|
||||
/// File attributes.
|
||||
///
|
||||
/// Note: This was called `filestat` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
record descriptor-stat {
|
||||
/// File type.
|
||||
%type: descriptor-type,
|
||||
/// Number of hard links to the file.
|
||||
link-count: link-count,
|
||||
/// For regular files, the file size in bytes. For symbolic links, the
|
||||
/// length in bytes of the pathname contained in the symbolic link.
|
||||
size: filesize,
|
||||
/// Last data access timestamp.
|
||||
///
|
||||
/// If the `option` is none, the platform doesn't maintain an access
|
||||
/// timestamp for this file.
|
||||
data-access-timestamp: option<datetime>,
|
||||
/// Last data modification timestamp.
|
||||
///
|
||||
/// If the `option` is none, the platform doesn't maintain a
|
||||
/// modification timestamp for this file.
|
||||
data-modification-timestamp: option<datetime>,
|
||||
/// Last file status-change timestamp.
|
||||
///
|
||||
/// If the `option` is none, the platform doesn't maintain a
|
||||
/// status-change timestamp for this file.
|
||||
status-change-timestamp: option<datetime>,
|
||||
}
|
||||
|
||||
/// Flags determining the method of how paths are resolved.
|
||||
@since(version = 0.2.0)
|
||||
flags path-flags {
|
||||
/// As long as the resolved path corresponds to a symbolic link, it is
|
||||
/// expanded.
|
||||
symlink-follow,
|
||||
}
|
||||
|
||||
/// Open flags used by `open-at`.
|
||||
@since(version = 0.2.0)
|
||||
flags open-flags {
|
||||
/// Create file if it does not exist, similar to `O_CREAT` in POSIX.
|
||||
create,
|
||||
/// Fail if not a directory, similar to `O_DIRECTORY` in POSIX.
|
||||
directory,
|
||||
/// Fail if file already exists, similar to `O_EXCL` in POSIX.
|
||||
exclusive,
|
||||
/// Truncate file to size 0, similar to `O_TRUNC` in POSIX.
|
||||
truncate,
|
||||
}
|
||||
|
||||
/// Number of hard links to an inode.
|
||||
@since(version = 0.2.0)
|
||||
type link-count = u64;
|
||||
|
||||
/// When setting a timestamp, this gives the value to set it to.
|
||||
@since(version = 0.2.0)
|
||||
variant new-timestamp {
|
||||
/// Leave the timestamp set to its previous value.
|
||||
no-change,
|
||||
/// Set the timestamp to the current time of the system clock associated
|
||||
/// with the filesystem.
|
||||
now,
|
||||
/// Set the timestamp to the given value.
|
||||
timestamp(datetime),
|
||||
}
|
||||
|
||||
/// A directory entry.
|
||||
record directory-entry {
|
||||
/// The type of the file referred to by this directory entry.
|
||||
%type: descriptor-type,
|
||||
|
||||
/// The name of the object.
|
||||
name: string,
|
||||
}
|
||||
|
||||
/// Error codes returned by functions, similar to `errno` in POSIX.
|
||||
/// Not all of these error codes are returned by the functions provided by this
|
||||
/// API; some are used in higher-level library layers, and others are provided
|
||||
/// merely for alignment with POSIX.
|
||||
enum error-code {
|
||||
/// Permission denied, similar to `EACCES` in POSIX.
|
||||
access,
|
||||
/// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX.
|
||||
would-block,
|
||||
/// Connection already in progress, similar to `EALREADY` in POSIX.
|
||||
already,
|
||||
/// Bad descriptor, similar to `EBADF` in POSIX.
|
||||
bad-descriptor,
|
||||
/// Device or resource busy, similar to `EBUSY` in POSIX.
|
||||
busy,
|
||||
/// Resource deadlock would occur, similar to `EDEADLK` in POSIX.
|
||||
deadlock,
|
||||
/// Storage quota exceeded, similar to `EDQUOT` in POSIX.
|
||||
quota,
|
||||
/// File exists, similar to `EEXIST` in POSIX.
|
||||
exist,
|
||||
/// File too large, similar to `EFBIG` in POSIX.
|
||||
file-too-large,
|
||||
/// Illegal byte sequence, similar to `EILSEQ` in POSIX.
|
||||
illegal-byte-sequence,
|
||||
/// Operation in progress, similar to `EINPROGRESS` in POSIX.
|
||||
in-progress,
|
||||
/// Interrupted function, similar to `EINTR` in POSIX.
|
||||
interrupted,
|
||||
/// Invalid argument, similar to `EINVAL` in POSIX.
|
||||
invalid,
|
||||
/// I/O error, similar to `EIO` in POSIX.
|
||||
io,
|
||||
/// Is a directory, similar to `EISDIR` in POSIX.
|
||||
is-directory,
|
||||
/// Too many levels of symbolic links, similar to `ELOOP` in POSIX.
|
||||
loop,
|
||||
/// Too many links, similar to `EMLINK` in POSIX.
|
||||
too-many-links,
|
||||
/// Message too large, similar to `EMSGSIZE` in POSIX.
|
||||
message-size,
|
||||
/// Filename too long, similar to `ENAMETOOLONG` in POSIX.
|
||||
name-too-long,
|
||||
/// No such device, similar to `ENODEV` in POSIX.
|
||||
no-device,
|
||||
/// No such file or directory, similar to `ENOENT` in POSIX.
|
||||
no-entry,
|
||||
/// No locks available, similar to `ENOLCK` in POSIX.
|
||||
no-lock,
|
||||
/// Not enough space, similar to `ENOMEM` in POSIX.
|
||||
insufficient-memory,
|
||||
/// No space left on device, similar to `ENOSPC` in POSIX.
|
||||
insufficient-space,
|
||||
/// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX.
|
||||
not-directory,
|
||||
/// Directory not empty, similar to `ENOTEMPTY` in POSIX.
|
||||
not-empty,
|
||||
/// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX.
|
||||
not-recoverable,
|
||||
/// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX.
|
||||
unsupported,
|
||||
/// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX.
|
||||
no-tty,
|
||||
/// No such device or address, similar to `ENXIO` in POSIX.
|
||||
no-such-device,
|
||||
/// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX.
|
||||
overflow,
|
||||
/// Operation not permitted, similar to `EPERM` in POSIX.
|
||||
not-permitted,
|
||||
/// Broken pipe, similar to `EPIPE` in POSIX.
|
||||
pipe,
|
||||
/// Read-only file system, similar to `EROFS` in POSIX.
|
||||
read-only,
|
||||
/// Invalid seek, similar to `ESPIPE` in POSIX.
|
||||
invalid-seek,
|
||||
/// Text file busy, similar to `ETXTBSY` in POSIX.
|
||||
text-file-busy,
|
||||
/// Cross-device link, similar to `EXDEV` in POSIX.
|
||||
cross-device,
|
||||
}
|
||||
|
||||
/// File or memory access pattern advisory information.
|
||||
@since(version = 0.2.0)
|
||||
enum advice {
|
||||
/// The application has no advice to give on its behavior with respect
|
||||
/// to the specified data.
|
||||
normal,
|
||||
/// The application expects to access the specified data sequentially
|
||||
/// from lower offsets to higher offsets.
|
||||
sequential,
|
||||
/// The application expects to access the specified data in a random
|
||||
/// order.
|
||||
random,
|
||||
/// The application expects to access the specified data in the near
|
||||
/// future.
|
||||
will-need,
|
||||
/// The application expects that it will not access the specified data
|
||||
/// in the near future.
|
||||
dont-need,
|
||||
/// The application expects to access the specified data once and then
|
||||
/// not reuse it thereafter.
|
||||
no-reuse,
|
||||
}
|
||||
|
||||
/// A 128-bit hash value, split into parts because wasm doesn't have a
|
||||
/// 128-bit integer type.
|
||||
@since(version = 0.2.0)
|
||||
record metadata-hash-value {
|
||||
/// 64 bits of a 128-bit hash value.
|
||||
lower: u64,
|
||||
/// Another 64 bits of a 128-bit hash value.
|
||||
upper: u64,
|
||||
}
|
||||
|
||||
/// A descriptor is a reference to a filesystem object, which may be a file,
|
||||
/// directory, named pipe, special file, or other object on which filesystem
|
||||
/// calls may be made.
|
||||
@since(version = 0.2.0)
|
||||
resource descriptor {
|
||||
/// Return a stream for reading from a file, if available.
|
||||
///
|
||||
/// May fail with an error-code describing why the file cannot be read.
|
||||
///
|
||||
/// Multiple read, write, and append streams may be active on the same open
|
||||
/// file and they do not interfere with each other.
|
||||
///
|
||||
/// Note: This allows using `read-stream`, which is similar to `read` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
read-via-stream: func(
|
||||
/// The offset within the file at which to start reading.
|
||||
offset: filesize,
|
||||
) -> result<input-stream, error-code>;
|
||||
|
||||
/// Return a stream for writing to a file, if available.
|
||||
///
|
||||
/// May fail with an error-code describing why the file cannot be written.
|
||||
///
|
||||
/// Note: This allows using `write-stream`, which is similar to `write` in
|
||||
/// POSIX.
|
||||
@since(version = 0.2.0)
|
||||
write-via-stream: func(
|
||||
/// The offset within the file at which to start writing.
|
||||
offset: filesize,
|
||||
) -> result<output-stream, error-code>;
|
||||
|
||||
/// Return a stream for appending to a file, if available.
|
||||
///
|
||||
/// May fail with an error-code describing why the file cannot be appended.
|
||||
///
|
||||
/// Note: This allows using `write-stream`, which is similar to `write` with
|
||||
/// `O_APPEND` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
append-via-stream: func() -> result<output-stream, error-code>;
|
||||
|
||||
/// Provide file advisory information on a descriptor.
|
||||
///
|
||||
/// This is similar to `posix_fadvise` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
advise: func(
|
||||
/// The offset within the file to which the advisory applies.
|
||||
offset: filesize,
|
||||
/// The length of the region to which the advisory applies.
|
||||
length: filesize,
|
||||
/// The advice.
|
||||
advice: advice
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Synchronize the data of a file to disk.
|
||||
///
|
||||
/// This function succeeds with no effect if the file descriptor is not
|
||||
/// opened for writing.
|
||||
///
|
||||
/// Note: This is similar to `fdatasync` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
sync-data: func() -> result<_, error-code>;
|
||||
|
||||
/// Get flags associated with a descriptor.
|
||||
///
|
||||
/// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX.
|
||||
///
|
||||
/// Note: This returns the value that was the `fs_flags` value returned
|
||||
/// from `fdstat_get` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
get-flags: func() -> result<descriptor-flags, error-code>;
|
||||
|
||||
/// Get the dynamic type of a descriptor.
|
||||
///
|
||||
/// Note: This returns the same value as the `type` field of the `fd-stat`
|
||||
/// returned by `stat`, `stat-at` and similar.
|
||||
///
|
||||
/// Note: This returns similar flags to the `st_mode & S_IFMT` value provided
|
||||
/// by `fstat` in POSIX.
|
||||
///
|
||||
/// Note: This returns the value that was the `fs_filetype` value returned
|
||||
/// from `fdstat_get` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
get-type: func() -> result<descriptor-type, error-code>;
|
||||
|
||||
/// Adjust the size of an open file. If this increases the file's size, the
|
||||
/// extra bytes are filled with zeros.
|
||||
///
|
||||
/// Note: This was called `fd_filestat_set_size` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
set-size: func(size: filesize) -> result<_, error-code>;
|
||||
|
||||
/// Adjust the timestamps of an open file or directory.
|
||||
///
|
||||
/// Note: This is similar to `futimens` in POSIX.
|
||||
///
|
||||
/// Note: This was called `fd_filestat_set_times` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
set-times: func(
|
||||
/// The desired values of the data access timestamp.
|
||||
data-access-timestamp: new-timestamp,
|
||||
/// The desired values of the data modification timestamp.
|
||||
data-modification-timestamp: new-timestamp,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Read from a descriptor, without using and updating the descriptor's offset.
|
||||
///
|
||||
/// This function returns a list of bytes containing the data that was
|
||||
/// read, along with a bool which, when true, indicates that the end of the
|
||||
/// file was reached. The returned list will contain up to `length` bytes; it
|
||||
/// may return fewer than requested, if the end of the file is reached or
|
||||
/// if the I/O operation is interrupted.
|
||||
///
|
||||
/// In the future, this may change to return a `stream<u8, error-code>`.
|
||||
///
|
||||
/// Note: This is similar to `pread` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
read: func(
|
||||
/// The maximum number of bytes to read.
|
||||
length: filesize,
|
||||
/// The offset within the file at which to read.
|
||||
offset: filesize,
|
||||
) -> result<tuple<list<u8>, bool>, error-code>;
|
||||
|
||||
/// Write to a descriptor, without using and updating the descriptor's offset.
|
||||
///
|
||||
/// It is valid to write past the end of a file; the file is extended to the
|
||||
/// extent of the write, with bytes between the previous end and the start of
|
||||
/// the write set to zero.
|
||||
///
|
||||
/// In the future, this may change to take a `stream<u8, error-code>`.
|
||||
///
|
||||
/// Note: This is similar to `pwrite` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
write: func(
|
||||
/// Data to write
|
||||
buffer: list<u8>,
|
||||
/// The offset within the file at which to write.
|
||||
offset: filesize,
|
||||
) -> result<filesize, error-code>;
|
||||
|
||||
/// Read directory entries from a directory.
|
||||
///
|
||||
/// On filesystems where directories contain entries referring to themselves
|
||||
/// and their parents, often named `.` and `..` respectively, these entries
|
||||
/// are omitted.
|
||||
///
|
||||
/// This always returns a new stream which starts at the beginning of the
|
||||
/// directory. Multiple streams may be active on the same directory, and they
|
||||
/// do not interfere with each other.
|
||||
@since(version = 0.2.0)
|
||||
read-directory: func() -> result<directory-entry-stream, error-code>;
|
||||
|
||||
/// Synchronize the data and metadata of a file to disk.
|
||||
///
|
||||
/// This function succeeds with no effect if the file descriptor is not
|
||||
/// opened for writing.
|
||||
///
|
||||
/// Note: This is similar to `fsync` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
sync: func() -> result<_, error-code>;
|
||||
|
||||
/// Create a directory.
|
||||
///
|
||||
/// Note: This is similar to `mkdirat` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
create-directory-at: func(
|
||||
/// The relative path at which to create the directory.
|
||||
path: string,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Return the attributes of an open file or directory.
|
||||
///
|
||||
/// Note: This is similar to `fstat` in POSIX, except that it does not return
|
||||
/// device and inode information. For testing whether two descriptors refer to
|
||||
/// the same underlying filesystem object, use `is-same-object`. To obtain
|
||||
/// additional data that can be used do determine whether a file has been
|
||||
/// modified, use `metadata-hash`.
|
||||
///
|
||||
/// Note: This was called `fd_filestat_get` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
stat: func() -> result<descriptor-stat, error-code>;
|
||||
|
||||
/// Return the attributes of a file or directory.
|
||||
///
|
||||
/// Note: This is similar to `fstatat` in POSIX, except that it does not
|
||||
/// return device and inode information. See the `stat` description for a
|
||||
/// discussion of alternatives.
|
||||
///
|
||||
/// Note: This was called `path_filestat_get` in earlier versions of WASI.
|
||||
@since(version = 0.2.0)
|
||||
stat-at: func(
|
||||
/// Flags determining the method of how the path is resolved.
|
||||
path-flags: path-flags,
|
||||
/// The relative path of the file or directory to inspect.
|
||||
path: string,
|
||||
) -> result<descriptor-stat, error-code>;
|
||||
|
||||
/// Adjust the timestamps of a file or directory.
|
||||
///
|
||||
/// Note: This is similar to `utimensat` in POSIX.
|
||||
///
|
||||
/// Note: This was called `path_filestat_set_times` in earlier versions of
|
||||
/// WASI.
|
||||
@since(version = 0.2.0)
|
||||
set-times-at: func(
|
||||
/// Flags determining the method of how the path is resolved.
|
||||
path-flags: path-flags,
|
||||
/// The relative path of the file or directory to operate on.
|
||||
path: string,
|
||||
/// The desired values of the data access timestamp.
|
||||
data-access-timestamp: new-timestamp,
|
||||
/// The desired values of the data modification timestamp.
|
||||
data-modification-timestamp: new-timestamp,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Create a hard link.
|
||||
///
|
||||
/// Fails with `error-code::no-entry` if the old path does not exist,
|
||||
/// with `error-code::exist` if the new path already exists, and
|
||||
/// `error-code::not-permitted` if the old path is not a file.
|
||||
///
|
||||
/// Note: This is similar to `linkat` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
link-at: func(
|
||||
/// Flags determining the method of how the path is resolved.
|
||||
old-path-flags: path-flags,
|
||||
/// The relative source path from which to link.
|
||||
old-path: string,
|
||||
/// The base directory for `new-path`.
|
||||
new-descriptor: borrow<descriptor>,
|
||||
/// The relative destination path at which to create the hard link.
|
||||
new-path: string,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Open a file or directory.
|
||||
///
|
||||
/// If `flags` contains `descriptor-flags::mutate-directory`, and the base
|
||||
/// descriptor doesn't have `descriptor-flags::mutate-directory` set,
|
||||
/// `open-at` fails with `error-code::read-only`.
|
||||
///
|
||||
/// If `flags` contains `write` or `mutate-directory`, or `open-flags`
|
||||
/// contains `truncate` or `create`, and the base descriptor doesn't have
|
||||
/// `descriptor-flags::mutate-directory` set, `open-at` fails with
|
||||
/// `error-code::read-only`.
|
||||
///
|
||||
/// Note: This is similar to `openat` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
open-at: func(
|
||||
/// Flags determining the method of how the path is resolved.
|
||||
path-flags: path-flags,
|
||||
/// The relative path of the object to open.
|
||||
path: string,
|
||||
/// The method by which to open the file.
|
||||
open-flags: open-flags,
|
||||
/// Flags to use for the resulting descriptor.
|
||||
%flags: descriptor-flags,
|
||||
) -> result<descriptor, error-code>;
|
||||
|
||||
/// Read the contents of a symbolic link.
|
||||
///
|
||||
/// If the contents contain an absolute or rooted path in the underlying
|
||||
/// filesystem, this function fails with `error-code::not-permitted`.
|
||||
///
|
||||
/// Note: This is similar to `readlinkat` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
readlink-at: func(
|
||||
/// The relative path of the symbolic link from which to read.
|
||||
path: string,
|
||||
) -> result<string, error-code>;
|
||||
|
||||
/// Remove a directory.
|
||||
///
|
||||
/// Return `error-code::not-empty` if the directory is not empty.
|
||||
///
|
||||
/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
remove-directory-at: func(
|
||||
/// The relative path to a directory to remove.
|
||||
path: string,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Rename a filesystem object.
|
||||
///
|
||||
/// Note: This is similar to `renameat` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
rename-at: func(
|
||||
/// The relative source path of the file or directory to rename.
|
||||
old-path: string,
|
||||
/// The base directory for `new-path`.
|
||||
new-descriptor: borrow<descriptor>,
|
||||
/// The relative destination path to which to rename the file or directory.
|
||||
new-path: string,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Create a symbolic link (also known as a "symlink").
|
||||
///
|
||||
/// If `old-path` starts with `/`, the function fails with
|
||||
/// `error-code::not-permitted`.
|
||||
///
|
||||
/// Note: This is similar to `symlinkat` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
symlink-at: func(
|
||||
/// The contents of the symbolic link.
|
||||
old-path: string,
|
||||
/// The relative destination path at which to create the symbolic link.
|
||||
new-path: string,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Unlink a filesystem object that is not a directory.
|
||||
///
|
||||
/// Return `error-code::is-directory` if the path refers to a directory.
|
||||
/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.
|
||||
@since(version = 0.2.0)
|
||||
unlink-file-at: func(
|
||||
/// The relative path to a file to unlink.
|
||||
path: string,
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Test whether two descriptors refer to the same filesystem object.
|
||||
///
|
||||
/// In POSIX, this corresponds to testing whether the two descriptors have the
|
||||
/// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers.
|
||||
/// wasi-filesystem does not expose device and inode numbers, so this function
|
||||
/// may be used instead.
|
||||
@since(version = 0.2.0)
|
||||
is-same-object: func(other: borrow<descriptor>) -> bool;
|
||||
|
||||
/// Return a hash of the metadata associated with a filesystem object referred
|
||||
/// to by a descriptor.
|
||||
///
|
||||
/// This returns a hash of the last-modification timestamp and file size, and
|
||||
/// may also include the inode number, device number, birth timestamp, and
|
||||
/// other metadata fields that may change when the file is modified or
|
||||
/// replaced. It may also include a secret value chosen by the
|
||||
/// implementation and not otherwise exposed.
|
||||
///
|
||||
/// Implementations are encouraged to provide the following properties:
|
||||
///
|
||||
/// - If the file is not modified or replaced, the computed hash value should
|
||||
/// usually not change.
|
||||
/// - If the object is modified or replaced, the computed hash value should
|
||||
/// usually change.
|
||||
/// - The inputs to the hash should not be easily computable from the
|
||||
/// computed hash.
|
||||
///
|
||||
/// However, none of these is required.
|
||||
@since(version = 0.2.0)
|
||||
metadata-hash: func() -> result<metadata-hash-value, error-code>;
|
||||
|
||||
/// Return a hash of the metadata associated with a filesystem object referred
|
||||
/// to by a directory descriptor and a relative path.
|
||||
///
|
||||
/// This performs the same hash computation as `metadata-hash`.
|
||||
@since(version = 0.2.0)
|
||||
metadata-hash-at: func(
|
||||
/// Flags determining the method of how the path is resolved.
|
||||
path-flags: path-flags,
|
||||
/// The relative path of the file or directory to inspect.
|
||||
path: string,
|
||||
) -> result<metadata-hash-value, error-code>;
|
||||
}
|
||||
|
||||
/// A stream of directory entries.
|
||||
@since(version = 0.2.0)
|
||||
resource directory-entry-stream {
|
||||
/// Read a single directory entry from a `directory-entry-stream`.
|
||||
@since(version = 0.2.0)
|
||||
read-directory-entry: func() -> result<option<directory-entry>, error-code>;
|
||||
}
|
||||
|
||||
/// Attempts to extract a filesystem-related `error-code` from the stream
|
||||
/// `error` provided.
|
||||
///
|
||||
/// Stream operations which return `stream-error::last-operation-failed`
|
||||
/// have a payload with more information about the operation that failed.
|
||||
/// This payload can be passed through to this function to see if there's
|
||||
/// filesystem-related information about the error to return.
|
||||
///
|
||||
/// Note that this function is fallible because not all stream-related
|
||||
/// errors are filesystem-related errors.
|
||||
@since(version = 0.2.0)
|
||||
filesystem-error-code: func(err: borrow<error>) -> option<error-code>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package wasi:filesystem@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
world imports {
|
||||
@since(version = 0.2.0)
|
||||
import types;
|
||||
@since(version = 0.2.0)
|
||||
import preopens;
|
||||
}
|
||||
49
crates/wasm-runtime-guest/wit/deps-0.2.6/http/handler.wit
Normal file
49
crates/wasm-runtime-guest/wit/deps-0.2.6/http/handler.wit
Normal file
@@ -0,0 +1,49 @@
|
||||
/// This interface defines a handler of incoming HTTP Requests. It should
|
||||
/// be exported by components which can respond to HTTP Requests.
|
||||
@since(version = 0.2.0)
|
||||
interface incoming-handler {
|
||||
@since(version = 0.2.0)
|
||||
use types.{incoming-request, response-outparam};
|
||||
|
||||
/// This function is invoked with an incoming HTTP Request, and a resource
|
||||
/// `response-outparam` which provides the capability to reply with an HTTP
|
||||
/// Response. The response is sent by calling the `response-outparam.set`
|
||||
/// method, which allows execution to continue after the response has been
|
||||
/// sent. This enables both streaming to the response body, and performing other
|
||||
/// work.
|
||||
///
|
||||
/// The implementor of this function must write a response to the
|
||||
/// `response-outparam` before returning, or else the caller will respond
|
||||
/// with an error on its behalf.
|
||||
@since(version = 0.2.0)
|
||||
handle: func(
|
||||
request: incoming-request,
|
||||
response-out: response-outparam
|
||||
);
|
||||
}
|
||||
|
||||
/// This interface defines a handler of outgoing HTTP Requests. It should be
|
||||
/// imported by components which wish to make HTTP Requests.
|
||||
@since(version = 0.2.0)
|
||||
interface outgoing-handler {
|
||||
@since(version = 0.2.0)
|
||||
use types.{
|
||||
outgoing-request, request-options, future-incoming-response, error-code
|
||||
};
|
||||
|
||||
/// This function is invoked with an outgoing HTTP Request, and it returns
|
||||
/// a resource `future-incoming-response` which represents an HTTP Response
|
||||
/// which may arrive in the future.
|
||||
///
|
||||
/// The `options` argument accepts optional parameters for the HTTP
|
||||
/// protocol's transport layer.
|
||||
///
|
||||
/// This function may return an error if the `outgoing-request` is invalid
|
||||
/// or not allowed to be made. Otherwise, protocol errors are reported
|
||||
/// through the `future-incoming-response`.
|
||||
@since(version = 0.2.0)
|
||||
handle: func(
|
||||
request: outgoing-request,
|
||||
options: option<request-options>
|
||||
) -> result<future-incoming-response, error-code>;
|
||||
}
|
||||
50
crates/wasm-runtime-guest/wit/deps-0.2.6/http/proxy.wit
Normal file
50
crates/wasm-runtime-guest/wit/deps-0.2.6/http/proxy.wit
Normal file
@@ -0,0 +1,50 @@
|
||||
package wasi:http@0.2.6;
|
||||
|
||||
/// The `wasi:http/imports` world imports all the APIs for HTTP proxies.
|
||||
/// It is intended to be `include`d in other worlds.
|
||||
@since(version = 0.2.0)
|
||||
world imports {
|
||||
/// HTTP proxies have access to time and randomness.
|
||||
@since(version = 0.2.0)
|
||||
import wasi:clocks/monotonic-clock@0.2.6;
|
||||
@since(version = 0.2.0)
|
||||
import wasi:clocks/wall-clock@0.2.6;
|
||||
@since(version = 0.2.0)
|
||||
import wasi:random/random@0.2.6;
|
||||
|
||||
/// Proxies have standard output and error streams which are expected to
|
||||
/// terminate in a developer-facing console provided by the host.
|
||||
@since(version = 0.2.0)
|
||||
import wasi:cli/stdout@0.2.6;
|
||||
@since(version = 0.2.0)
|
||||
import wasi:cli/stderr@0.2.6;
|
||||
|
||||
/// TODO: this is a temporary workaround until component tooling is able to
|
||||
/// gracefully handle the absence of stdin. Hosts must return an eof stream
|
||||
/// for this import, which is what wasi-libc + tooling will do automatically
|
||||
/// when this import is properly removed.
|
||||
@since(version = 0.2.0)
|
||||
import wasi:cli/stdin@0.2.6;
|
||||
|
||||
/// This is the default handler to use when user code simply wants to make an
|
||||
/// HTTP request (e.g., via `fetch()`).
|
||||
@since(version = 0.2.0)
|
||||
import outgoing-handler;
|
||||
}
|
||||
|
||||
/// The `wasi:http/proxy` world captures a widely-implementable intersection of
|
||||
/// hosts that includes HTTP forward and reverse proxies. Components targeting
|
||||
/// this world may concurrently stream in and out any number of incoming and
|
||||
/// outgoing HTTP requests.
|
||||
@since(version = 0.2.0)
|
||||
world proxy {
|
||||
@since(version = 0.2.0)
|
||||
include imports;
|
||||
|
||||
/// The host delivers incoming HTTP requests to a component by calling the
|
||||
/// `handle` function of this exported interface. A host may arbitrarily reuse
|
||||
/// or not reuse component instance when delivering incoming HTTP requests and
|
||||
/// thus a component must be able to handle 0..N calls to `handle`.
|
||||
@since(version = 0.2.0)
|
||||
export incoming-handler;
|
||||
}
|
||||
688
crates/wasm-runtime-guest/wit/deps-0.2.6/http/types.wit
Normal file
688
crates/wasm-runtime-guest/wit/deps-0.2.6/http/types.wit
Normal file
@@ -0,0 +1,688 @@
|
||||
/// This interface defines all of the types and methods for implementing
|
||||
/// HTTP Requests and Responses, both incoming and outgoing, as well as
|
||||
/// their headers, trailers, and bodies.
|
||||
@since(version = 0.2.0)
|
||||
interface types {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:clocks/monotonic-clock@0.2.6.{duration};
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/streams@0.2.6.{input-stream, output-stream};
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/error@0.2.6.{error as io-error};
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/poll@0.2.6.{pollable};
|
||||
|
||||
/// This type corresponds to HTTP standard Methods.
|
||||
@since(version = 0.2.0)
|
||||
variant method {
|
||||
get,
|
||||
head,
|
||||
post,
|
||||
put,
|
||||
delete,
|
||||
connect,
|
||||
options,
|
||||
trace,
|
||||
patch,
|
||||
other(string)
|
||||
}
|
||||
|
||||
/// This type corresponds to HTTP standard Related Schemes.
|
||||
@since(version = 0.2.0)
|
||||
variant scheme {
|
||||
HTTP,
|
||||
HTTPS,
|
||||
other(string)
|
||||
}
|
||||
|
||||
/// These cases are inspired by the IANA HTTP Proxy Error Types:
|
||||
/// <https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types>
|
||||
@since(version = 0.2.0)
|
||||
variant error-code {
|
||||
DNS-timeout,
|
||||
DNS-error(DNS-error-payload),
|
||||
destination-not-found,
|
||||
destination-unavailable,
|
||||
destination-IP-prohibited,
|
||||
destination-IP-unroutable,
|
||||
connection-refused,
|
||||
connection-terminated,
|
||||
connection-timeout,
|
||||
connection-read-timeout,
|
||||
connection-write-timeout,
|
||||
connection-limit-reached,
|
||||
TLS-protocol-error,
|
||||
TLS-certificate-error,
|
||||
TLS-alert-received(TLS-alert-received-payload),
|
||||
HTTP-request-denied,
|
||||
HTTP-request-length-required,
|
||||
HTTP-request-body-size(option<u64>),
|
||||
HTTP-request-method-invalid,
|
||||
HTTP-request-URI-invalid,
|
||||
HTTP-request-URI-too-long,
|
||||
HTTP-request-header-section-size(option<u32>),
|
||||
HTTP-request-header-size(option<field-size-payload>),
|
||||
HTTP-request-trailer-section-size(option<u32>),
|
||||
HTTP-request-trailer-size(field-size-payload),
|
||||
HTTP-response-incomplete,
|
||||
HTTP-response-header-section-size(option<u32>),
|
||||
HTTP-response-header-size(field-size-payload),
|
||||
HTTP-response-body-size(option<u64>),
|
||||
HTTP-response-trailer-section-size(option<u32>),
|
||||
HTTP-response-trailer-size(field-size-payload),
|
||||
HTTP-response-transfer-coding(option<string>),
|
||||
HTTP-response-content-coding(option<string>),
|
||||
HTTP-response-timeout,
|
||||
HTTP-upgrade-failed,
|
||||
HTTP-protocol-error,
|
||||
loop-detected,
|
||||
configuration-error,
|
||||
/// This is a catch-all error for anything that doesn't fit cleanly into a
|
||||
/// more specific case. It also includes an optional string for an
|
||||
/// unstructured description of the error. Users should not depend on the
|
||||
/// string for diagnosing errors, as it's not required to be consistent
|
||||
/// between implementations.
|
||||
internal-error(option<string>)
|
||||
}
|
||||
|
||||
/// Defines the case payload type for `DNS-error` above:
|
||||
@since(version = 0.2.0)
|
||||
record DNS-error-payload {
|
||||
rcode: option<string>,
|
||||
info-code: option<u16>
|
||||
}
|
||||
|
||||
/// Defines the case payload type for `TLS-alert-received` above:
|
||||
@since(version = 0.2.0)
|
||||
record TLS-alert-received-payload {
|
||||
alert-id: option<u8>,
|
||||
alert-message: option<string>
|
||||
}
|
||||
|
||||
/// Defines the case payload type for `HTTP-response-{header,trailer}-size` above:
|
||||
@since(version = 0.2.0)
|
||||
record field-size-payload {
|
||||
field-name: option<string>,
|
||||
field-size: option<u32>
|
||||
}
|
||||
|
||||
/// Attempts to extract a http-related `error` from the wasi:io `error`
|
||||
/// provided.
|
||||
///
|
||||
/// Stream operations which return
|
||||
/// `wasi:io/stream/stream-error::last-operation-failed` have a payload of
|
||||
/// type `wasi:io/error/error` with more information about the operation
|
||||
/// that failed. This payload can be passed through to this function to see
|
||||
/// if there's http-related information about the error to return.
|
||||
///
|
||||
/// Note that this function is fallible because not all io-errors are
|
||||
/// http-related errors.
|
||||
@since(version = 0.2.0)
|
||||
http-error-code: func(err: borrow<io-error>) -> option<error-code>;
|
||||
|
||||
/// This type enumerates the different kinds of errors that may occur when
|
||||
/// setting or appending to a `fields` resource.
|
||||
@since(version = 0.2.0)
|
||||
variant header-error {
|
||||
/// This error indicates that a `field-name` or `field-value` was
|
||||
/// syntactically invalid when used with an operation that sets headers in a
|
||||
/// `fields`.
|
||||
invalid-syntax,
|
||||
|
||||
/// This error indicates that a forbidden `field-name` was used when trying
|
||||
/// to set a header in a `fields`.
|
||||
forbidden,
|
||||
|
||||
/// This error indicates that the operation on the `fields` was not
|
||||
/// permitted because the fields are immutable.
|
||||
immutable,
|
||||
}
|
||||
|
||||
/// Field names are always strings.
|
||||
///
|
||||
/// Field names should always be treated as case insensitive by the `fields`
|
||||
/// resource for the purposes of equality checking.
|
||||
@since(version = 0.2.1)
|
||||
type field-name = field-key;
|
||||
|
||||
/// Field keys are always strings.
|
||||
///
|
||||
/// Field keys should always be treated as case insensitive by the `fields`
|
||||
/// resource for the purposes of equality checking.
|
||||
///
|
||||
/// # Deprecation
|
||||
///
|
||||
/// This type has been deprecated in favor of the `field-name` type.
|
||||
@since(version = 0.2.0)
|
||||
@deprecated(version = 0.2.2)
|
||||
type field-key = string;
|
||||
|
||||
/// Field values should always be ASCII strings. However, in
|
||||
/// reality, HTTP implementations often have to interpret malformed values,
|
||||
/// so they are provided as a list of bytes.
|
||||
@since(version = 0.2.0)
|
||||
type field-value = list<u8>;
|
||||
|
||||
/// This following block defines the `fields` resource which corresponds to
|
||||
/// HTTP standard Fields. Fields are a common representation used for both
|
||||
/// Headers and Trailers.
|
||||
///
|
||||
/// A `fields` may be mutable or immutable. A `fields` created using the
|
||||
/// constructor, `from-list`, or `clone` will be mutable, but a `fields`
|
||||
/// resource given by other means (including, but not limited to,
|
||||
/// `incoming-request.headers`, `outgoing-request.headers`) might be
|
||||
/// immutable. In an immutable fields, the `set`, `append`, and `delete`
|
||||
/// operations will fail with `header-error.immutable`.
|
||||
@since(version = 0.2.0)
|
||||
resource fields {
|
||||
|
||||
/// Construct an empty HTTP Fields.
|
||||
///
|
||||
/// The resulting `fields` is mutable.
|
||||
@since(version = 0.2.0)
|
||||
constructor();
|
||||
|
||||
/// Construct an HTTP Fields.
|
||||
///
|
||||
/// The resulting `fields` is mutable.
|
||||
///
|
||||
/// The list represents each name-value pair in the Fields. Names
|
||||
/// which have multiple values are represented by multiple entries in this
|
||||
/// list with the same name.
|
||||
///
|
||||
/// The tuple is a pair of the field name, represented as a string, and
|
||||
/// Value, represented as a list of bytes.
|
||||
///
|
||||
/// An error result will be returned if any `field-name` or `field-value` is
|
||||
/// syntactically invalid, or if a field is forbidden.
|
||||
@since(version = 0.2.0)
|
||||
from-list: static func(
|
||||
entries: list<tuple<field-name,field-value>>
|
||||
) -> result<fields, header-error>;
|
||||
|
||||
/// Get all of the values corresponding to a name. If the name is not present
|
||||
/// in this `fields` or is syntactically invalid, an empty list is returned.
|
||||
/// However, if the name is present but empty, this is represented by a list
|
||||
/// with one or more empty field-values present.
|
||||
@since(version = 0.2.0)
|
||||
get: func(name: field-name) -> list<field-value>;
|
||||
|
||||
/// Returns `true` when the name is present in this `fields`. If the name is
|
||||
/// syntactically invalid, `false` is returned.
|
||||
@since(version = 0.2.0)
|
||||
has: func(name: field-name) -> bool;
|
||||
|
||||
/// Set all of the values for a name. Clears any existing values for that
|
||||
/// name, if they have been set.
|
||||
///
|
||||
/// Fails with `header-error.immutable` if the `fields` are immutable.
|
||||
///
|
||||
/// Fails with `header-error.invalid-syntax` if the `field-name` or any of
|
||||
/// the `field-value`s are syntactically invalid.
|
||||
@since(version = 0.2.0)
|
||||
set: func(name: field-name, value: list<field-value>) -> result<_, header-error>;
|
||||
|
||||
/// Delete all values for a name. Does nothing if no values for the name
|
||||
/// exist.
|
||||
///
|
||||
/// Fails with `header-error.immutable` if the `fields` are immutable.
|
||||
///
|
||||
/// Fails with `header-error.invalid-syntax` if the `field-name` is
|
||||
/// syntactically invalid.
|
||||
@since(version = 0.2.0)
|
||||
delete: func(name: field-name) -> result<_, header-error>;
|
||||
|
||||
/// Append a value for a name. Does not change or delete any existing
|
||||
/// values for that name.
|
||||
///
|
||||
/// Fails with `header-error.immutable` if the `fields` are immutable.
|
||||
///
|
||||
/// Fails with `header-error.invalid-syntax` if the `field-name` or
|
||||
/// `field-value` are syntactically invalid.
|
||||
@since(version = 0.2.0)
|
||||
append: func(name: field-name, value: field-value) -> result<_, header-error>;
|
||||
|
||||
/// Retrieve the full set of names and values in the Fields. Like the
|
||||
/// constructor, the list represents each name-value pair.
|
||||
///
|
||||
/// The outer list represents each name-value pair in the Fields. Names
|
||||
/// which have multiple values are represented by multiple entries in this
|
||||
/// list with the same name.
|
||||
///
|
||||
/// The names and values are always returned in the original casing and in
|
||||
/// the order in which they will be serialized for transport.
|
||||
@since(version = 0.2.0)
|
||||
entries: func() -> list<tuple<field-name,field-value>>;
|
||||
|
||||
/// Make a deep copy of the Fields. Equivalent in behavior to calling the
|
||||
/// `fields` constructor on the return value of `entries`. The resulting
|
||||
/// `fields` is mutable.
|
||||
@since(version = 0.2.0)
|
||||
clone: func() -> fields;
|
||||
}
|
||||
|
||||
/// Headers is an alias for Fields.
|
||||
@since(version = 0.2.0)
|
||||
type headers = fields;
|
||||
|
||||
/// Trailers is an alias for Fields.
|
||||
@since(version = 0.2.0)
|
||||
type trailers = fields;
|
||||
|
||||
/// Represents an incoming HTTP Request.
|
||||
@since(version = 0.2.0)
|
||||
resource incoming-request {
|
||||
|
||||
/// Returns the method of the incoming request.
|
||||
@since(version = 0.2.0)
|
||||
method: func() -> method;
|
||||
|
||||
/// Returns the path with query parameters from the request, as a string.
|
||||
@since(version = 0.2.0)
|
||||
path-with-query: func() -> option<string>;
|
||||
|
||||
/// Returns the protocol scheme from the request.
|
||||
@since(version = 0.2.0)
|
||||
scheme: func() -> option<scheme>;
|
||||
|
||||
/// Returns the authority of the Request's target URI, if present.
|
||||
@since(version = 0.2.0)
|
||||
authority: func() -> option<string>;
|
||||
|
||||
/// Get the `headers` associated with the request.
|
||||
///
|
||||
/// The returned `headers` resource is immutable: `set`, `append`, and
|
||||
/// `delete` operations will fail with `header-error.immutable`.
|
||||
///
|
||||
/// The `headers` returned are a child resource: it must be dropped before
|
||||
/// the parent `incoming-request` is dropped. Dropping this
|
||||
/// `incoming-request` before all children are dropped will trap.
|
||||
@since(version = 0.2.0)
|
||||
headers: func() -> headers;
|
||||
|
||||
/// Gives the `incoming-body` associated with this request. Will only
|
||||
/// return success at most once, and subsequent calls will return error.
|
||||
@since(version = 0.2.0)
|
||||
consume: func() -> result<incoming-body>;
|
||||
}
|
||||
|
||||
/// Represents an outgoing HTTP Request.
|
||||
@since(version = 0.2.0)
|
||||
resource outgoing-request {
|
||||
|
||||
/// Construct a new `outgoing-request` with a default `method` of `GET`, and
|
||||
/// `none` values for `path-with-query`, `scheme`, and `authority`.
|
||||
///
|
||||
/// * `headers` is the HTTP Headers for the Request.
|
||||
///
|
||||
/// It is possible to construct, or manipulate with the accessor functions
|
||||
/// below, an `outgoing-request` with an invalid combination of `scheme`
|
||||
/// and `authority`, or `headers` which are not permitted to be sent.
|
||||
/// It is the obligation of the `outgoing-handler.handle` implementation
|
||||
/// to reject invalid constructions of `outgoing-request`.
|
||||
@since(version = 0.2.0)
|
||||
constructor(
|
||||
headers: headers
|
||||
);
|
||||
|
||||
/// Returns the resource corresponding to the outgoing Body for this
|
||||
/// Request.
|
||||
///
|
||||
/// Returns success on the first call: the `outgoing-body` resource for
|
||||
/// this `outgoing-request` can be retrieved at most once. Subsequent
|
||||
/// calls will return error.
|
||||
@since(version = 0.2.0)
|
||||
body: func() -> result<outgoing-body>;
|
||||
|
||||
/// Get the Method for the Request.
|
||||
@since(version = 0.2.0)
|
||||
method: func() -> method;
|
||||
/// Set the Method for the Request. Fails if the string present in a
|
||||
/// `method.other` argument is not a syntactically valid method.
|
||||
@since(version = 0.2.0)
|
||||
set-method: func(method: method) -> result;
|
||||
|
||||
/// Get the combination of the HTTP Path and Query for the Request.
|
||||
/// When `none`, this represents an empty Path and empty Query.
|
||||
@since(version = 0.2.0)
|
||||
path-with-query: func() -> option<string>;
|
||||
/// Set the combination of the HTTP Path and Query for the Request.
|
||||
/// When `none`, this represents an empty Path and empty Query. Fails is the
|
||||
/// string given is not a syntactically valid path and query uri component.
|
||||
@since(version = 0.2.0)
|
||||
set-path-with-query: func(path-with-query: option<string>) -> result;
|
||||
|
||||
/// Get the HTTP Related Scheme for the Request. When `none`, the
|
||||
/// implementation may choose an appropriate default scheme.
|
||||
@since(version = 0.2.0)
|
||||
scheme: func() -> option<scheme>;
|
||||
/// Set the HTTP Related Scheme for the Request. When `none`, the
|
||||
/// implementation may choose an appropriate default scheme. Fails if the
|
||||
/// string given is not a syntactically valid uri scheme.
|
||||
@since(version = 0.2.0)
|
||||
set-scheme: func(scheme: option<scheme>) -> result;
|
||||
|
||||
/// Get the authority of the Request's target URI. A value of `none` may be used
|
||||
/// with Related Schemes which do not require an authority. The HTTP and
|
||||
/// HTTPS schemes always require an authority.
|
||||
@since(version = 0.2.0)
|
||||
authority: func() -> option<string>;
|
||||
/// Set the authority of the Request's target URI. A value of `none` may be used
|
||||
/// with Related Schemes which do not require an authority. The HTTP and
|
||||
/// HTTPS schemes always require an authority. Fails if the string given is
|
||||
/// not a syntactically valid URI authority.
|
||||
@since(version = 0.2.0)
|
||||
set-authority: func(authority: option<string>) -> result;
|
||||
|
||||
/// Get the headers associated with the Request.
|
||||
///
|
||||
/// The returned `headers` resource is immutable: `set`, `append`, and
|
||||
/// `delete` operations will fail with `header-error.immutable`.
|
||||
///
|
||||
/// This headers resource is a child: it must be dropped before the parent
|
||||
/// `outgoing-request` is dropped, or its ownership is transferred to
|
||||
/// another component by e.g. `outgoing-handler.handle`.
|
||||
@since(version = 0.2.0)
|
||||
headers: func() -> headers;
|
||||
}
|
||||
|
||||
/// Parameters for making an HTTP Request. Each of these parameters is
|
||||
/// currently an optional timeout applicable to the transport layer of the
|
||||
/// HTTP protocol.
|
||||
///
|
||||
/// These timeouts are separate from any the user may use to bound a
|
||||
/// blocking call to `wasi:io/poll.poll`.
|
||||
@since(version = 0.2.0)
|
||||
resource request-options {
|
||||
/// Construct a default `request-options` value.
|
||||
@since(version = 0.2.0)
|
||||
constructor();
|
||||
|
||||
/// The timeout for the initial connect to the HTTP Server.
|
||||
@since(version = 0.2.0)
|
||||
connect-timeout: func() -> option<duration>;
|
||||
|
||||
/// Set the timeout for the initial connect to the HTTP Server. An error
|
||||
/// return value indicates that this timeout is not supported.
|
||||
@since(version = 0.2.0)
|
||||
set-connect-timeout: func(duration: option<duration>) -> result;
|
||||
|
||||
/// The timeout for receiving the first byte of the Response body.
|
||||
@since(version = 0.2.0)
|
||||
first-byte-timeout: func() -> option<duration>;
|
||||
|
||||
/// Set the timeout for receiving the first byte of the Response body. An
|
||||
/// error return value indicates that this timeout is not supported.
|
||||
@since(version = 0.2.0)
|
||||
set-first-byte-timeout: func(duration: option<duration>) -> result;
|
||||
|
||||
/// The timeout for receiving subsequent chunks of bytes in the Response
|
||||
/// body stream.
|
||||
@since(version = 0.2.0)
|
||||
between-bytes-timeout: func() -> option<duration>;
|
||||
|
||||
/// Set the timeout for receiving subsequent chunks of bytes in the Response
|
||||
/// body stream. An error return value indicates that this timeout is not
|
||||
/// supported.
|
||||
@since(version = 0.2.0)
|
||||
set-between-bytes-timeout: func(duration: option<duration>) -> result;
|
||||
}
|
||||
|
||||
/// Represents the ability to send an HTTP Response.
|
||||
///
|
||||
/// This resource is used by the `wasi:http/incoming-handler` interface to
|
||||
/// allow a Response to be sent corresponding to the Request provided as the
|
||||
/// other argument to `incoming-handler.handle`.
|
||||
@since(version = 0.2.0)
|
||||
resource response-outparam {
|
||||
/// Send an HTTP 1xx response.
|
||||
///
|
||||
/// Unlike `response-outparam.set`, this does not consume the
|
||||
/// `response-outparam`, allowing the guest to send an arbitrary number of
|
||||
/// informational responses before sending the final response using
|
||||
/// `response-outparam.set`.
|
||||
///
|
||||
/// This will return an `HTTP-protocol-error` if `status` is not in the
|
||||
/// range [100-199], or an `internal-error` if the implementation does not
|
||||
/// support informational responses.
|
||||
@unstable(feature = informational-outbound-responses)
|
||||
send-informational: func(
|
||||
status: u16,
|
||||
headers: headers
|
||||
) -> result<_, error-code>;
|
||||
|
||||
/// Set the value of the `response-outparam` to either send a response,
|
||||
/// or indicate an error.
|
||||
///
|
||||
/// This method consumes the `response-outparam` to ensure that it is
|
||||
/// called at most once. If it is never called, the implementation
|
||||
/// will respond with an error.
|
||||
///
|
||||
/// The user may provide an `error` to `response` to allow the
|
||||
/// implementation determine how to respond with an HTTP error response.
|
||||
@since(version = 0.2.0)
|
||||
set: static func(
|
||||
param: response-outparam,
|
||||
response: result<outgoing-response, error-code>,
|
||||
);
|
||||
}
|
||||
|
||||
/// This type corresponds to the HTTP standard Status Code.
|
||||
@since(version = 0.2.0)
|
||||
type status-code = u16;
|
||||
|
||||
/// Represents an incoming HTTP Response.
|
||||
@since(version = 0.2.0)
|
||||
resource incoming-response {
|
||||
|
||||
/// Returns the status code from the incoming response.
|
||||
@since(version = 0.2.0)
|
||||
status: func() -> status-code;
|
||||
|
||||
/// Returns the headers from the incoming response.
|
||||
///
|
||||
/// The returned `headers` resource is immutable: `set`, `append`, and
|
||||
/// `delete` operations will fail with `header-error.immutable`.
|
||||
///
|
||||
/// This headers resource is a child: it must be dropped before the parent
|
||||
/// `incoming-response` is dropped.
|
||||
@since(version = 0.2.0)
|
||||
headers: func() -> headers;
|
||||
|
||||
/// Returns the incoming body. May be called at most once. Returns error
|
||||
/// if called additional times.
|
||||
@since(version = 0.2.0)
|
||||
consume: func() -> result<incoming-body>;
|
||||
}
|
||||
|
||||
/// Represents an incoming HTTP Request or Response's Body.
|
||||
///
|
||||
/// A body has both its contents - a stream of bytes - and a (possibly
|
||||
/// empty) set of trailers, indicating that the full contents of the
|
||||
/// body have been received. This resource represents the contents as
|
||||
/// an `input-stream` and the delivery of trailers as a `future-trailers`,
|
||||
/// and ensures that the user of this interface may only be consuming either
|
||||
/// the body contents or waiting on trailers at any given time.
|
||||
@since(version = 0.2.0)
|
||||
resource incoming-body {
|
||||
|
||||
/// Returns the contents of the body, as a stream of bytes.
|
||||
///
|
||||
/// Returns success on first call: the stream representing the contents
|
||||
/// can be retrieved at most once. Subsequent calls will return error.
|
||||
///
|
||||
/// The returned `input-stream` resource is a child: it must be dropped
|
||||
/// before the parent `incoming-body` is dropped, or consumed by
|
||||
/// `incoming-body.finish`.
|
||||
///
|
||||
/// This invariant ensures that the implementation can determine whether
|
||||
/// the user is consuming the contents of the body, waiting on the
|
||||
/// `future-trailers` to be ready, or neither. This allows for network
|
||||
/// backpressure is to be applied when the user is consuming the body,
|
||||
/// and for that backpressure to not inhibit delivery of the trailers if
|
||||
/// the user does not read the entire body.
|
||||
@since(version = 0.2.0)
|
||||
%stream: func() -> result<input-stream>;
|
||||
|
||||
/// Takes ownership of `incoming-body`, and returns a `future-trailers`.
|
||||
/// This function will trap if the `input-stream` child is still alive.
|
||||
@since(version = 0.2.0)
|
||||
finish: static func(this: incoming-body) -> future-trailers;
|
||||
}
|
||||
|
||||
/// Represents a future which may eventually return trailers, or an error.
|
||||
///
|
||||
/// In the case that the incoming HTTP Request or Response did not have any
|
||||
/// trailers, this future will resolve to the empty set of trailers once the
|
||||
/// complete Request or Response body has been received.
|
||||
@since(version = 0.2.0)
|
||||
resource future-trailers {
|
||||
|
||||
/// Returns a pollable which becomes ready when either the trailers have
|
||||
/// been received, or an error has occurred. When this pollable is ready,
|
||||
/// the `get` method will return `some`.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
|
||||
/// Returns the contents of the trailers, or an error which occurred,
|
||||
/// once the future is ready.
|
||||
///
|
||||
/// The outer `option` represents future readiness. Users can wait on this
|
||||
/// `option` to become `some` using the `subscribe` method.
|
||||
///
|
||||
/// The outer `result` is used to retrieve the trailers or error at most
|
||||
/// once. It will be success on the first call in which the outer option
|
||||
/// is `some`, and error on subsequent calls.
|
||||
///
|
||||
/// The inner `result` represents that either the HTTP Request or Response
|
||||
/// body, as well as any trailers, were received successfully, or that an
|
||||
/// error occurred receiving them. The optional `trailers` indicates whether
|
||||
/// or not trailers were present in the body.
|
||||
///
|
||||
/// When some `trailers` are returned by this method, the `trailers`
|
||||
/// resource is immutable, and a child. Use of the `set`, `append`, or
|
||||
/// `delete` methods will return an error, and the resource must be
|
||||
/// dropped before the parent `future-trailers` is dropped.
|
||||
@since(version = 0.2.0)
|
||||
get: func() -> option<result<result<option<trailers>, error-code>>>;
|
||||
}
|
||||
|
||||
/// Represents an outgoing HTTP Response.
|
||||
@since(version = 0.2.0)
|
||||
resource outgoing-response {
|
||||
|
||||
/// Construct an `outgoing-response`, with a default `status-code` of `200`.
|
||||
/// If a different `status-code` is needed, it must be set via the
|
||||
/// `set-status-code` method.
|
||||
///
|
||||
/// * `headers` is the HTTP Headers for the Response.
|
||||
@since(version = 0.2.0)
|
||||
constructor(headers: headers);
|
||||
|
||||
/// Get the HTTP Status Code for the Response.
|
||||
@since(version = 0.2.0)
|
||||
status-code: func() -> status-code;
|
||||
|
||||
/// Set the HTTP Status Code for the Response. Fails if the status-code
|
||||
/// given is not a valid http status code.
|
||||
@since(version = 0.2.0)
|
||||
set-status-code: func(status-code: status-code) -> result;
|
||||
|
||||
/// Get the headers associated with the Request.
|
||||
///
|
||||
/// The returned `headers` resource is immutable: `set`, `append`, and
|
||||
/// `delete` operations will fail with `header-error.immutable`.
|
||||
///
|
||||
/// This headers resource is a child: it must be dropped before the parent
|
||||
/// `outgoing-request` is dropped, or its ownership is transferred to
|
||||
/// another component by e.g. `outgoing-handler.handle`.
|
||||
@since(version = 0.2.0)
|
||||
headers: func() -> headers;
|
||||
|
||||
/// Returns the resource corresponding to the outgoing Body for this Response.
|
||||
///
|
||||
/// Returns success on the first call: the `outgoing-body` resource for
|
||||
/// this `outgoing-response` can be retrieved at most once. Subsequent
|
||||
/// calls will return error.
|
||||
@since(version = 0.2.0)
|
||||
body: func() -> result<outgoing-body>;
|
||||
}
|
||||
|
||||
/// Represents an outgoing HTTP Request or Response's Body.
|
||||
///
|
||||
/// A body has both its contents - a stream of bytes - and a (possibly
|
||||
/// empty) set of trailers, inducating the full contents of the body
|
||||
/// have been sent. This resource represents the contents as an
|
||||
/// `output-stream` child resource, and the completion of the body (with
|
||||
/// optional trailers) with a static function that consumes the
|
||||
/// `outgoing-body` resource, and ensures that the user of this interface
|
||||
/// may not write to the body contents after the body has been finished.
|
||||
///
|
||||
/// If the user code drops this resource, as opposed to calling the static
|
||||
/// method `finish`, the implementation should treat the body as incomplete,
|
||||
/// and that an error has occurred. The implementation should propagate this
|
||||
/// error to the HTTP protocol by whatever means it has available,
|
||||
/// including: corrupting the body on the wire, aborting the associated
|
||||
/// Request, or sending a late status code for the Response.
|
||||
@since(version = 0.2.0)
|
||||
resource outgoing-body {
|
||||
|
||||
/// Returns a stream for writing the body contents.
|
||||
///
|
||||
/// The returned `output-stream` is a child resource: it must be dropped
|
||||
/// before the parent `outgoing-body` resource is dropped (or finished),
|
||||
/// otherwise the `outgoing-body` drop or `finish` will trap.
|
||||
///
|
||||
/// Returns success on the first call: the `output-stream` resource for
|
||||
/// this `outgoing-body` may be retrieved at most once. Subsequent calls
|
||||
/// will return error.
|
||||
@since(version = 0.2.0)
|
||||
write: func() -> result<output-stream>;
|
||||
|
||||
/// Finalize an outgoing body, optionally providing trailers. This must be
|
||||
/// called to signal that the response is complete. If the `outgoing-body`
|
||||
/// is dropped without calling `outgoing-body.finalize`, the implementation
|
||||
/// should treat the body as corrupted.
|
||||
///
|
||||
/// Fails if the body's `outgoing-request` or `outgoing-response` was
|
||||
/// constructed with a Content-Length header, and the contents written
|
||||
/// to the body (via `write`) does not match the value given in the
|
||||
/// Content-Length.
|
||||
@since(version = 0.2.0)
|
||||
finish: static func(
|
||||
this: outgoing-body,
|
||||
trailers: option<trailers>
|
||||
) -> result<_, error-code>;
|
||||
}
|
||||
|
||||
/// Represents a future which may eventually return an incoming HTTP
|
||||
/// Response, or an error.
|
||||
///
|
||||
/// This resource is returned by the `wasi:http/outgoing-handler` interface to
|
||||
/// provide the HTTP Response corresponding to the sent Request.
|
||||
@since(version = 0.2.0)
|
||||
resource future-incoming-response {
|
||||
/// Returns a pollable which becomes ready when either the Response has
|
||||
/// been received, or an error has occurred. When this pollable is ready,
|
||||
/// the `get` method will return `some`.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
|
||||
/// Returns the incoming HTTP Response, or an error, once one is ready.
|
||||
///
|
||||
/// The outer `option` represents future readiness. Users can wait on this
|
||||
/// `option` to become `some` using the `subscribe` method.
|
||||
///
|
||||
/// The outer `result` is used to retrieve the response or error at most
|
||||
/// once. It will be success on the first call in which the outer option
|
||||
/// is `some`, and error on subsequent calls.
|
||||
///
|
||||
/// The inner `result` represents that either the incoming HTTP Response
|
||||
/// status and headers have received successfully, or that an error
|
||||
/// occurred. Errors may also occur while consuming the response body,
|
||||
/// but those will be reported by the `incoming-body` and its
|
||||
/// `output-stream` child.
|
||||
@since(version = 0.2.0)
|
||||
get: func() -> option<result<result<incoming-response, error-code>>>;
|
||||
}
|
||||
}
|
||||
34
crates/wasm-runtime-guest/wit/deps-0.2.6/io/error.wit
Normal file
34
crates/wasm-runtime-guest/wit/deps-0.2.6/io/error.wit
Normal file
@@ -0,0 +1,34 @@
|
||||
package wasi:io@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
interface error {
|
||||
/// A resource which represents some error information.
|
||||
///
|
||||
/// The only method provided by this resource is `to-debug-string`,
|
||||
/// which provides some human-readable information about the error.
|
||||
///
|
||||
/// In the `wasi:io` package, this resource is returned through the
|
||||
/// `wasi:io/streams/stream-error` type.
|
||||
///
|
||||
/// To provide more specific error information, other interfaces may
|
||||
/// offer functions to "downcast" this error into more specific types. For example,
|
||||
/// errors returned from streams derived from filesystem types can be described using
|
||||
/// the filesystem's own error-code type. This is done using the function
|
||||
/// `wasi:filesystem/types/filesystem-error-code`, which takes a `borrow<error>`
|
||||
/// parameter and returns an `option<wasi:filesystem/types/error-code>`.
|
||||
///
|
||||
/// The set of functions which can "downcast" an `error` into a more
|
||||
/// concrete type is open.
|
||||
@since(version = 0.2.0)
|
||||
resource error {
|
||||
/// Returns a string that is suitable to assist humans in debugging
|
||||
/// this error.
|
||||
///
|
||||
/// WARNING: The returned string should not be consumed mechanically!
|
||||
/// It may change across platforms, hosts, or other implementation
|
||||
/// details. Parsing this string is a major platform-compatibility
|
||||
/// hazard.
|
||||
@since(version = 0.2.0)
|
||||
to-debug-string: func() -> string;
|
||||
}
|
||||
}
|
||||
47
crates/wasm-runtime-guest/wit/deps-0.2.6/io/poll.wit
Normal file
47
crates/wasm-runtime-guest/wit/deps-0.2.6/io/poll.wit
Normal file
@@ -0,0 +1,47 @@
|
||||
package wasi:io@0.2.6;
|
||||
|
||||
/// A poll API intended to let users wait for I/O events on multiple handles
|
||||
/// at once.
|
||||
@since(version = 0.2.0)
|
||||
interface poll {
|
||||
/// `pollable` represents a single I/O event which may be ready, or not.
|
||||
@since(version = 0.2.0)
|
||||
resource pollable {
|
||||
|
||||
/// Return the readiness of a pollable. This function never blocks.
|
||||
///
|
||||
/// Returns `true` when the pollable is ready, and `false` otherwise.
|
||||
@since(version = 0.2.0)
|
||||
ready: func() -> bool;
|
||||
|
||||
/// `block` returns immediately if the pollable is ready, and otherwise
|
||||
/// blocks until ready.
|
||||
///
|
||||
/// This function is equivalent to calling `poll.poll` on a list
|
||||
/// containing only this pollable.
|
||||
@since(version = 0.2.0)
|
||||
block: func();
|
||||
}
|
||||
|
||||
/// Poll for completion on a set of pollables.
|
||||
///
|
||||
/// This function takes a list of pollables, which identify I/O sources of
|
||||
/// interest, and waits until one or more of the events is ready for I/O.
|
||||
///
|
||||
/// The result `list<u32>` contains one or more indices of handles in the
|
||||
/// argument list that is ready for I/O.
|
||||
///
|
||||
/// This function traps if either:
|
||||
/// - the list is empty, or:
|
||||
/// - the list contains more elements than can be indexed with a `u32` value.
|
||||
///
|
||||
/// A timeout can be implemented by adding a pollable from the
|
||||
/// wasi-clocks API to the list.
|
||||
///
|
||||
/// This function does not return a `result`; polling in itself does not
|
||||
/// do any I/O so it doesn't fail. If any of the I/O sources identified by
|
||||
/// the pollables has an error, it is indicated by marking the source as
|
||||
/// being ready for I/O.
|
||||
@since(version = 0.2.0)
|
||||
poll: func(in: list<borrow<pollable>>) -> list<u32>;
|
||||
}
|
||||
290
crates/wasm-runtime-guest/wit/deps-0.2.6/io/streams.wit
Normal file
290
crates/wasm-runtime-guest/wit/deps-0.2.6/io/streams.wit
Normal file
@@ -0,0 +1,290 @@
|
||||
package wasi:io@0.2.6;
|
||||
|
||||
/// WASI I/O is an I/O abstraction API which is currently focused on providing
|
||||
/// stream types.
|
||||
///
|
||||
/// In the future, the component model is expected to add built-in stream types;
|
||||
/// when it does, they are expected to subsume this API.
|
||||
@since(version = 0.2.0)
|
||||
interface streams {
|
||||
@since(version = 0.2.0)
|
||||
use error.{error};
|
||||
@since(version = 0.2.0)
|
||||
use poll.{pollable};
|
||||
|
||||
/// An error for input-stream and output-stream operations.
|
||||
@since(version = 0.2.0)
|
||||
variant stream-error {
|
||||
/// The last operation (a write or flush) failed before completion.
|
||||
///
|
||||
/// More information is available in the `error` payload.
|
||||
///
|
||||
/// After this, the stream will be closed. All future operations return
|
||||
/// `stream-error::closed`.
|
||||
last-operation-failed(error),
|
||||
/// The stream is closed: no more input will be accepted by the
|
||||
/// stream. A closed output-stream will return this error on all
|
||||
/// future operations.
|
||||
closed
|
||||
}
|
||||
|
||||
/// An input bytestream.
|
||||
///
|
||||
/// `input-stream`s are *non-blocking* to the extent practical on underlying
|
||||
/// platforms. I/O operations always return promptly; if fewer bytes are
|
||||
/// promptly available than requested, they return the number of bytes promptly
|
||||
/// available, which could even be zero. To wait for data to be available,
|
||||
/// use the `subscribe` function to obtain a `pollable` which can be polled
|
||||
/// for using `wasi:io/poll`.
|
||||
@since(version = 0.2.0)
|
||||
resource input-stream {
|
||||
/// Perform a non-blocking read from the stream.
|
||||
///
|
||||
/// When the source of a `read` is binary data, the bytes from the source
|
||||
/// are returned verbatim. When the source of a `read` is known to the
|
||||
/// implementation to be text, bytes containing the UTF-8 encoding of the
|
||||
/// text are returned.
|
||||
///
|
||||
/// This function returns a list of bytes containing the read data,
|
||||
/// when successful. The returned list will contain up to `len` bytes;
|
||||
/// it may return fewer than requested, but not more. The list is
|
||||
/// empty when no bytes are available for reading at this time. The
|
||||
/// pollable given by `subscribe` will be ready when more bytes are
|
||||
/// available.
|
||||
///
|
||||
/// This function fails with a `stream-error` when the operation
|
||||
/// encounters an error, giving `last-operation-failed`, or when the
|
||||
/// stream is closed, giving `closed`.
|
||||
///
|
||||
/// When the caller gives a `len` of 0, it represents a request to
|
||||
/// read 0 bytes. If the stream is still open, this call should
|
||||
/// succeed and return an empty list, or otherwise fail with `closed`.
|
||||
///
|
||||
/// The `len` parameter is a `u64`, which could represent a list of u8 which
|
||||
/// is not possible to allocate in wasm32, or not desirable to allocate as
|
||||
/// as a return value by the callee. The callee may return a list of bytes
|
||||
/// less than `len` in size while more bytes are available for reading.
|
||||
@since(version = 0.2.0)
|
||||
read: func(
|
||||
/// The maximum number of bytes to read
|
||||
len: u64
|
||||
) -> result<list<u8>, stream-error>;
|
||||
|
||||
/// Read bytes from a stream, after blocking until at least one byte can
|
||||
/// be read. Except for blocking, behavior is identical to `read`.
|
||||
@since(version = 0.2.0)
|
||||
blocking-read: func(
|
||||
/// The maximum number of bytes to read
|
||||
len: u64
|
||||
) -> result<list<u8>, stream-error>;
|
||||
|
||||
/// Skip bytes from a stream. Returns number of bytes skipped.
|
||||
///
|
||||
/// Behaves identical to `read`, except instead of returning a list
|
||||
/// of bytes, returns the number of bytes consumed from the stream.
|
||||
@since(version = 0.2.0)
|
||||
skip: func(
|
||||
/// The maximum number of bytes to skip.
|
||||
len: u64,
|
||||
) -> result<u64, stream-error>;
|
||||
|
||||
/// Skip bytes from a stream, after blocking until at least one byte
|
||||
/// can be skipped. Except for blocking behavior, identical to `skip`.
|
||||
@since(version = 0.2.0)
|
||||
blocking-skip: func(
|
||||
/// The maximum number of bytes to skip.
|
||||
len: u64,
|
||||
) -> result<u64, stream-error>;
|
||||
|
||||
/// Create a `pollable` which will resolve once either the specified stream
|
||||
/// has bytes available to read or the other end of the stream has been
|
||||
/// closed.
|
||||
/// The created `pollable` is a child resource of the `input-stream`.
|
||||
/// Implementations may trap if the `input-stream` is dropped before
|
||||
/// all derived `pollable`s created with this function are dropped.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
}
|
||||
|
||||
|
||||
/// An output bytestream.
|
||||
///
|
||||
/// `output-stream`s are *non-blocking* to the extent practical on
|
||||
/// underlying platforms. Except where specified otherwise, I/O operations also
|
||||
/// always return promptly, after the number of bytes that can be written
|
||||
/// promptly, which could even be zero. To wait for the stream to be ready to
|
||||
/// accept data, the `subscribe` function to obtain a `pollable` which can be
|
||||
/// polled for using `wasi:io/poll`.
|
||||
///
|
||||
/// Dropping an `output-stream` while there's still an active write in
|
||||
/// progress may result in the data being lost. Before dropping the stream,
|
||||
/// be sure to fully flush your writes.
|
||||
@since(version = 0.2.0)
|
||||
resource output-stream {
|
||||
/// Check readiness for writing. This function never blocks.
|
||||
///
|
||||
/// Returns the number of bytes permitted for the next call to `write`,
|
||||
/// or an error. Calling `write` with more bytes than this function has
|
||||
/// permitted will trap.
|
||||
///
|
||||
/// When this function returns 0 bytes, the `subscribe` pollable will
|
||||
/// become ready when this function will report at least 1 byte, or an
|
||||
/// error.
|
||||
@since(version = 0.2.0)
|
||||
check-write: func() -> result<u64, stream-error>;
|
||||
|
||||
/// Perform a write. This function never blocks.
|
||||
///
|
||||
/// When the destination of a `write` is binary data, the bytes from
|
||||
/// `contents` are written verbatim. When the destination of a `write` is
|
||||
/// known to the implementation to be text, the bytes of `contents` are
|
||||
/// transcoded from UTF-8 into the encoding of the destination and then
|
||||
/// written.
|
||||
///
|
||||
/// Precondition: check-write gave permit of Ok(n) and contents has a
|
||||
/// length of less than or equal to n. Otherwise, this function will trap.
|
||||
///
|
||||
/// returns Err(closed) without writing if the stream has closed since
|
||||
/// the last call to check-write provided a permit.
|
||||
@since(version = 0.2.0)
|
||||
write: func(
|
||||
contents: list<u8>
|
||||
) -> result<_, stream-error>;
|
||||
|
||||
/// Perform a write of up to 4096 bytes, and then flush the stream. Block
|
||||
/// until all of these operations are complete, or an error occurs.
|
||||
///
|
||||
/// This is a convenience wrapper around the use of `check-write`,
|
||||
/// `subscribe`, `write`, and `flush`, and is implemented with the
|
||||
/// following pseudo-code:
|
||||
///
|
||||
/// ```text
|
||||
/// let pollable = this.subscribe();
|
||||
/// while !contents.is_empty() {
|
||||
/// // Wait for the stream to become writable
|
||||
/// pollable.block();
|
||||
/// let Ok(n) = this.check-write(); // eliding error handling
|
||||
/// let len = min(n, contents.len());
|
||||
/// let (chunk, rest) = contents.split_at(len);
|
||||
/// this.write(chunk ); // eliding error handling
|
||||
/// contents = rest;
|
||||
/// }
|
||||
/// this.flush();
|
||||
/// // Wait for completion of `flush`
|
||||
/// pollable.block();
|
||||
/// // Check for any errors that arose during `flush`
|
||||
/// let _ = this.check-write(); // eliding error handling
|
||||
/// ```
|
||||
@since(version = 0.2.0)
|
||||
blocking-write-and-flush: func(
|
||||
contents: list<u8>
|
||||
) -> result<_, stream-error>;
|
||||
|
||||
/// Request to flush buffered output. This function never blocks.
|
||||
///
|
||||
/// This tells the output-stream that the caller intends any buffered
|
||||
/// output to be flushed. the output which is expected to be flushed
|
||||
/// is all that has been passed to `write` prior to this call.
|
||||
///
|
||||
/// Upon calling this function, the `output-stream` will not accept any
|
||||
/// writes (`check-write` will return `ok(0)`) until the flush has
|
||||
/// completed. The `subscribe` pollable will become ready when the
|
||||
/// flush has completed and the stream can accept more writes.
|
||||
@since(version = 0.2.0)
|
||||
flush: func() -> result<_, stream-error>;
|
||||
|
||||
/// Request to flush buffered output, and block until flush completes
|
||||
/// and stream is ready for writing again.
|
||||
@since(version = 0.2.0)
|
||||
blocking-flush: func() -> result<_, stream-error>;
|
||||
|
||||
/// Create a `pollable` which will resolve once the output-stream
|
||||
/// is ready for more writing, or an error has occurred. When this
|
||||
/// pollable is ready, `check-write` will return `ok(n)` with n>0, or an
|
||||
/// error.
|
||||
///
|
||||
/// If the stream is closed, this pollable is always ready immediately.
|
||||
///
|
||||
/// The created `pollable` is a child resource of the `output-stream`.
|
||||
/// Implementations may trap if the `output-stream` is dropped before
|
||||
/// all derived `pollable`s created with this function are dropped.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
|
||||
/// Write zeroes to a stream.
|
||||
///
|
||||
/// This should be used precisely like `write` with the exact same
|
||||
/// preconditions (must use check-write first), but instead of
|
||||
/// passing a list of bytes, you simply pass the number of zero-bytes
|
||||
/// that should be written.
|
||||
@since(version = 0.2.0)
|
||||
write-zeroes: func(
|
||||
/// The number of zero-bytes to write
|
||||
len: u64
|
||||
) -> result<_, stream-error>;
|
||||
|
||||
/// Perform a write of up to 4096 zeroes, and then flush the stream.
|
||||
/// Block until all of these operations are complete, or an error
|
||||
/// occurs.
|
||||
///
|
||||
/// This is a convenience wrapper around the use of `check-write`,
|
||||
/// `subscribe`, `write-zeroes`, and `flush`, and is implemented with
|
||||
/// the following pseudo-code:
|
||||
///
|
||||
/// ```text
|
||||
/// let pollable = this.subscribe();
|
||||
/// while num_zeroes != 0 {
|
||||
/// // Wait for the stream to become writable
|
||||
/// pollable.block();
|
||||
/// let Ok(n) = this.check-write(); // eliding error handling
|
||||
/// let len = min(n, num_zeroes);
|
||||
/// this.write-zeroes(len); // eliding error handling
|
||||
/// num_zeroes -= len;
|
||||
/// }
|
||||
/// this.flush();
|
||||
/// // Wait for completion of `flush`
|
||||
/// pollable.block();
|
||||
/// // Check for any errors that arose during `flush`
|
||||
/// let _ = this.check-write(); // eliding error handling
|
||||
/// ```
|
||||
@since(version = 0.2.0)
|
||||
blocking-write-zeroes-and-flush: func(
|
||||
/// The number of zero-bytes to write
|
||||
len: u64
|
||||
) -> result<_, stream-error>;
|
||||
|
||||
/// Read from one stream and write to another.
|
||||
///
|
||||
/// The behavior of splice is equivalent to:
|
||||
/// 1. calling `check-write` on the `output-stream`
|
||||
/// 2. calling `read` on the `input-stream` with the smaller of the
|
||||
/// `check-write` permitted length and the `len` provided to `splice`
|
||||
/// 3. calling `write` on the `output-stream` with that read data.
|
||||
///
|
||||
/// Any error reported by the call to `check-write`, `read`, or
|
||||
/// `write` ends the splice and reports that error.
|
||||
///
|
||||
/// This function returns the number of bytes transferred; it may be less
|
||||
/// than `len`.
|
||||
@since(version = 0.2.0)
|
||||
splice: func(
|
||||
/// The stream to read from
|
||||
src: borrow<input-stream>,
|
||||
/// The number of bytes to splice
|
||||
len: u64,
|
||||
) -> result<u64, stream-error>;
|
||||
|
||||
/// Read from one stream and write to another, with blocking.
|
||||
///
|
||||
/// This is similar to `splice`, except that it blocks until the
|
||||
/// `output-stream` is ready for writing, and the `input-stream`
|
||||
/// is ready for reading, before performing the `splice`.
|
||||
@since(version = 0.2.0)
|
||||
blocking-splice: func(
|
||||
/// The stream to read from
|
||||
src: borrow<input-stream>,
|
||||
/// The number of bytes to splice
|
||||
len: u64,
|
||||
) -> result<u64, stream-error>;
|
||||
}
|
||||
}
|
||||
10
crates/wasm-runtime-guest/wit/deps-0.2.6/io/world.wit
Normal file
10
crates/wasm-runtime-guest/wit/deps-0.2.6/io/world.wit
Normal file
@@ -0,0 +1,10 @@
|
||||
package wasi:io@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
world imports {
|
||||
@since(version = 0.2.0)
|
||||
import streams;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
import poll;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package wasi:random@0.2.6;
|
||||
/// The insecure-seed interface for seeding hash-map DoS resistance.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
@since(version = 0.2.0)
|
||||
interface insecure-seed {
|
||||
/// Return a 128-bit value that may contain a pseudo-random value.
|
||||
///
|
||||
/// The returned value is not required to be computed from a CSPRNG, and may
|
||||
/// even be entirely deterministic. Host implementations are encouraged to
|
||||
/// provide pseudo-random values to any program exposed to
|
||||
/// attacker-controlled content, to enable DoS protection built into many
|
||||
/// languages' hash-map implementations.
|
||||
///
|
||||
/// This function is intended to only be called once, by a source language
|
||||
/// to initialize Denial Of Service (DoS) protection in its hash-map
|
||||
/// implementation.
|
||||
///
|
||||
/// # Expected future evolution
|
||||
///
|
||||
/// This will likely be changed to a value import, to prevent it from being
|
||||
/// called multiple times and potentially used for purposes other than DoS
|
||||
/// protection.
|
||||
@since(version = 0.2.0)
|
||||
insecure-seed: func() -> tuple<u64, u64>;
|
||||
}
|
||||
25
crates/wasm-runtime-guest/wit/deps-0.2.6/random/insecure.wit
Normal file
25
crates/wasm-runtime-guest/wit/deps-0.2.6/random/insecure.wit
Normal file
@@ -0,0 +1,25 @@
|
||||
package wasi:random@0.2.6;
|
||||
/// The insecure interface for insecure pseudo-random numbers.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
@since(version = 0.2.0)
|
||||
interface insecure {
|
||||
/// Return `len` insecure pseudo-random bytes.
|
||||
///
|
||||
/// This function is not cryptographically secure. Do not use it for
|
||||
/// anything related to security.
|
||||
///
|
||||
/// There are no requirements on the values of the returned bytes, however
|
||||
/// implementations are encouraged to return evenly distributed values with
|
||||
/// a long period.
|
||||
@since(version = 0.2.0)
|
||||
get-insecure-random-bytes: func(len: u64) -> list<u8>;
|
||||
|
||||
/// Return an insecure pseudo-random `u64` value.
|
||||
///
|
||||
/// This function returns the same type of pseudo-random data as
|
||||
/// `get-insecure-random-bytes`, represented as a `u64`.
|
||||
@since(version = 0.2.0)
|
||||
get-insecure-random-u64: func() -> u64;
|
||||
}
|
||||
29
crates/wasm-runtime-guest/wit/deps-0.2.6/random/random.wit
Normal file
29
crates/wasm-runtime-guest/wit/deps-0.2.6/random/random.wit
Normal file
@@ -0,0 +1,29 @@
|
||||
package wasi:random@0.2.6;
|
||||
/// WASI Random is a random data API.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
@since(version = 0.2.0)
|
||||
interface random {
|
||||
/// Return `len` cryptographically-secure random or pseudo-random bytes.
|
||||
///
|
||||
/// This function must produce data at least as cryptographically secure and
|
||||
/// fast as an adequately seeded cryptographically-secure pseudo-random
|
||||
/// number generator (CSPRNG). It must not block, from the perspective of
|
||||
/// the calling program, under any circumstances, including on the first
|
||||
/// request and on requests for numbers of bytes. The returned data must
|
||||
/// always be unpredictable.
|
||||
///
|
||||
/// This function must always return fresh data. Deterministic environments
|
||||
/// must omit this function, rather than implementing it with deterministic
|
||||
/// data.
|
||||
@since(version = 0.2.0)
|
||||
get-random-bytes: func(len: u64) -> list<u8>;
|
||||
|
||||
/// Return a cryptographically-secure random or pseudo-random `u64` value.
|
||||
///
|
||||
/// This function returns the same type of data as `get-random-bytes`,
|
||||
/// represented as a `u64`.
|
||||
@since(version = 0.2.0)
|
||||
get-random-u64: func() -> u64;
|
||||
}
|
||||
13
crates/wasm-runtime-guest/wit/deps-0.2.6/random/world.wit
Normal file
13
crates/wasm-runtime-guest/wit/deps-0.2.6/random/world.wit
Normal file
@@ -0,0 +1,13 @@
|
||||
package wasi:random@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
world imports {
|
||||
@since(version = 0.2.0)
|
||||
import random;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
import insecure;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
import insecure-seed;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
/// This interface provides a value-export of the default network handle..
|
||||
@since(version = 0.2.0)
|
||||
interface instance-network {
|
||||
@since(version = 0.2.0)
|
||||
use network.{network};
|
||||
|
||||
/// Get a handle to the default network.
|
||||
@since(version = 0.2.0)
|
||||
instance-network: func() -> network;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
@since(version = 0.2.0)
|
||||
interface ip-name-lookup {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/poll@0.2.6.{pollable};
|
||||
@since(version = 0.2.0)
|
||||
use network.{network, error-code, ip-address};
|
||||
|
||||
/// Resolve an internet host name to a list of IP addresses.
|
||||
///
|
||||
/// Unicode domain names are automatically converted to ASCII using IDNA encoding.
|
||||
/// If the input is an IP address string, the address is parsed and returned
|
||||
/// as-is without making any external requests.
|
||||
///
|
||||
/// See the wasi-socket proposal README.md for a comparison with getaddrinfo.
|
||||
///
|
||||
/// This function never blocks. It either immediately fails or immediately
|
||||
/// returns successfully with a `resolve-address-stream` that can be used
|
||||
/// to (asynchronously) fetch the results.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address.
|
||||
///
|
||||
/// # References:
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html>
|
||||
/// - <https://man7.org/linux/man-pages/man3/getaddrinfo.3.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=getaddrinfo&sektion=3>
|
||||
@since(version = 0.2.0)
|
||||
resolve-addresses: func(network: borrow<network>, name: string) -> result<resolve-address-stream, error-code>;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
resource resolve-address-stream {
|
||||
/// Returns the next address from the resolver.
|
||||
///
|
||||
/// This function should be called multiple times. On each call, it will
|
||||
/// return the next address in connection order preference. If all
|
||||
/// addresses have been exhausted, this function returns `none`.
|
||||
///
|
||||
/// This function never returns IPv4-mapped IPv6 addresses.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY)
|
||||
/// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN)
|
||||
/// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL)
|
||||
/// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN)
|
||||
@since(version = 0.2.0)
|
||||
resolve-next-address: func() -> result<option<ip-address>, error-code>;
|
||||
|
||||
/// Create a `pollable` which will resolve once the stream is ready for I/O.
|
||||
///
|
||||
/// Note: this function is here for WASI 0.2 only.
|
||||
/// It's planned to be removed when `future` is natively supported in Preview3.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
}
|
||||
}
|
||||
169
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/network.wit
Normal file
169
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/network.wit
Normal file
@@ -0,0 +1,169 @@
|
||||
@since(version = 0.2.0)
|
||||
interface network {
|
||||
@unstable(feature = network-error-code)
|
||||
use wasi:io/error@0.2.6.{error};
|
||||
|
||||
/// An opaque resource that represents access to (a subset of) the network.
|
||||
/// This enables context-based security for networking.
|
||||
/// There is no need for this to map 1:1 to a physical network interface.
|
||||
@since(version = 0.2.0)
|
||||
resource network;
|
||||
|
||||
/// Error codes.
|
||||
///
|
||||
/// In theory, every API can return any error code.
|
||||
/// In practice, API's typically only return the errors documented per API
|
||||
/// combined with a couple of errors that are always possible:
|
||||
/// - `unknown`
|
||||
/// - `access-denied`
|
||||
/// - `not-supported`
|
||||
/// - `out-of-memory`
|
||||
/// - `concurrency-conflict`
|
||||
///
|
||||
/// See each individual API for what the POSIX equivalents are. They sometimes differ per API.
|
||||
@since(version = 0.2.0)
|
||||
enum error-code {
|
||||
/// Unknown error
|
||||
unknown,
|
||||
|
||||
/// Access denied.
|
||||
///
|
||||
/// POSIX equivalent: EACCES, EPERM
|
||||
access-denied,
|
||||
|
||||
/// The operation is not supported.
|
||||
///
|
||||
/// POSIX equivalent: EOPNOTSUPP
|
||||
not-supported,
|
||||
|
||||
/// One of the arguments is invalid.
|
||||
///
|
||||
/// POSIX equivalent: EINVAL
|
||||
invalid-argument,
|
||||
|
||||
/// Not enough memory to complete the operation.
|
||||
///
|
||||
/// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY
|
||||
out-of-memory,
|
||||
|
||||
/// The operation timed out before it could finish completely.
|
||||
timeout,
|
||||
|
||||
/// This operation is incompatible with another asynchronous operation that is already in progress.
|
||||
///
|
||||
/// POSIX equivalent: EALREADY
|
||||
concurrency-conflict,
|
||||
|
||||
/// Trying to finish an asynchronous operation that:
|
||||
/// - has not been started yet, or:
|
||||
/// - was already finished by a previous `finish-*` call.
|
||||
///
|
||||
/// Note: this is scheduled to be removed when `future`s are natively supported.
|
||||
not-in-progress,
|
||||
|
||||
/// The operation has been aborted because it could not be completed immediately.
|
||||
///
|
||||
/// Note: this is scheduled to be removed when `future`s are natively supported.
|
||||
would-block,
|
||||
|
||||
|
||||
/// The operation is not valid in the socket's current state.
|
||||
invalid-state,
|
||||
|
||||
/// A new socket resource could not be created because of a system limit.
|
||||
new-socket-limit,
|
||||
|
||||
/// A bind operation failed because the provided address is not an address that the `network` can bind to.
|
||||
address-not-bindable,
|
||||
|
||||
/// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available.
|
||||
address-in-use,
|
||||
|
||||
/// The remote address is not reachable
|
||||
remote-unreachable,
|
||||
|
||||
|
||||
/// The TCP connection was forcefully rejected
|
||||
connection-refused,
|
||||
|
||||
/// The TCP connection was reset.
|
||||
connection-reset,
|
||||
|
||||
/// A TCP connection was aborted.
|
||||
connection-aborted,
|
||||
|
||||
|
||||
/// The size of a datagram sent to a UDP socket exceeded the maximum
|
||||
/// supported size.
|
||||
datagram-too-large,
|
||||
|
||||
|
||||
/// Name does not exist or has no suitable associated IP addresses.
|
||||
name-unresolvable,
|
||||
|
||||
/// A temporary failure in name resolution occurred.
|
||||
temporary-resolver-failure,
|
||||
|
||||
/// A permanent failure in name resolution occurred.
|
||||
permanent-resolver-failure,
|
||||
}
|
||||
|
||||
/// Attempts to extract a network-related `error-code` from the stream
|
||||
/// `error` provided.
|
||||
///
|
||||
/// Stream operations which return `stream-error::last-operation-failed`
|
||||
/// have a payload with more information about the operation that failed.
|
||||
/// This payload can be passed through to this function to see if there's
|
||||
/// network-related information about the error to return.
|
||||
///
|
||||
/// Note that this function is fallible because not all stream-related
|
||||
/// errors are network-related errors.
|
||||
@unstable(feature = network-error-code)
|
||||
network-error-code: func(err: borrow<error>) -> option<error-code>;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
enum ip-address-family {
|
||||
/// Similar to `AF_INET` in POSIX.
|
||||
ipv4,
|
||||
|
||||
/// Similar to `AF_INET6` in POSIX.
|
||||
ipv6,
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
type ipv4-address = tuple<u8, u8, u8, u8>;
|
||||
@since(version = 0.2.0)
|
||||
type ipv6-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
variant ip-address {
|
||||
ipv4(ipv4-address),
|
||||
ipv6(ipv6-address),
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
record ipv4-socket-address {
|
||||
/// sin_port
|
||||
port: u16,
|
||||
/// sin_addr
|
||||
address: ipv4-address,
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
record ipv6-socket-address {
|
||||
/// sin6_port
|
||||
port: u16,
|
||||
/// sin6_flowinfo
|
||||
flow-info: u32,
|
||||
/// sin6_addr
|
||||
address: ipv6-address,
|
||||
/// sin6_scope_id
|
||||
scope-id: u32,
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
variant ip-socket-address {
|
||||
ipv4(ipv4-socket-address),
|
||||
ipv6(ipv6-socket-address),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
@since(version = 0.2.0)
|
||||
interface tcp-create-socket {
|
||||
@since(version = 0.2.0)
|
||||
use network.{network, error-code, ip-address-family};
|
||||
@since(version = 0.2.0)
|
||||
use tcp.{tcp-socket};
|
||||
|
||||
/// Create a new TCP socket.
|
||||
///
|
||||
/// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX.
|
||||
/// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise.
|
||||
///
|
||||
/// This function does not require a network capability handle. This is considered to be safe because
|
||||
/// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect`
|
||||
/// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world.
|
||||
///
|
||||
/// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT)
|
||||
/// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/socket.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>
|
||||
@since(version = 0.2.0)
|
||||
create-tcp-socket: func(address-family: ip-address-family) -> result<tcp-socket, error-code>;
|
||||
}
|
||||
387
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/tcp.wit
Normal file
387
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/tcp.wit
Normal file
@@ -0,0 +1,387 @@
|
||||
@since(version = 0.2.0)
|
||||
interface tcp {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/streams@0.2.6.{input-stream, output-stream};
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/poll@0.2.6.{pollable};
|
||||
@since(version = 0.2.0)
|
||||
use wasi:clocks/monotonic-clock@0.2.6.{duration};
|
||||
@since(version = 0.2.0)
|
||||
use network.{network, error-code, ip-socket-address, ip-address-family};
|
||||
|
||||
@since(version = 0.2.0)
|
||||
enum shutdown-type {
|
||||
/// Similar to `SHUT_RD` in POSIX.
|
||||
receive,
|
||||
|
||||
/// Similar to `SHUT_WR` in POSIX.
|
||||
send,
|
||||
|
||||
/// Similar to `SHUT_RDWR` in POSIX.
|
||||
both,
|
||||
}
|
||||
|
||||
/// A TCP socket resource.
|
||||
///
|
||||
/// The socket can be in one of the following states:
|
||||
/// - `unbound`
|
||||
/// - `bind-in-progress`
|
||||
/// - `bound` (See note below)
|
||||
/// - `listen-in-progress`
|
||||
/// - `listening`
|
||||
/// - `connect-in-progress`
|
||||
/// - `connected`
|
||||
/// - `closed`
|
||||
/// See <https://github.com/WebAssembly/wasi-sockets/blob/main/TcpSocketOperationalSemantics.md>
|
||||
/// for more information.
|
||||
///
|
||||
/// Note: Except where explicitly mentioned, whenever this documentation uses
|
||||
/// the term "bound" without backticks it actually means: in the `bound` state *or higher*.
|
||||
/// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`)
|
||||
///
|
||||
/// In addition to the general error codes documented on the
|
||||
/// `network::error-code` type, TCP socket methods may always return
|
||||
/// `error(invalid-state)` when in the `closed` state.
|
||||
@since(version = 0.2.0)
|
||||
resource tcp-socket {
|
||||
/// Bind the socket to a specific network on the provided IP address and port.
|
||||
///
|
||||
/// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which
|
||||
/// network interface(s) to bind to.
|
||||
/// If the TCP/UDP port is zero, the socket will be bound to a random free port.
|
||||
///
|
||||
/// Bind can be attempted multiple times on the same socket, even with
|
||||
/// different arguments on each iteration. But never concurrently and
|
||||
/// only as long as the previous bind failed. Once a bind succeeds, the
|
||||
/// binding can't be changed anymore.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)
|
||||
/// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL)
|
||||
/// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL)
|
||||
/// - `invalid-state`: The socket is already bound. (EINVAL)
|
||||
/// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows)
|
||||
/// - `address-in-use`: Address is already in use. (EADDRINUSE)
|
||||
/// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL)
|
||||
/// - `not-in-progress`: A `bind` operation is not in progress.
|
||||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
|
||||
///
|
||||
/// # Implementors note
|
||||
/// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT
|
||||
/// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR
|
||||
/// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior
|
||||
/// and SO_REUSEADDR performs something different entirely.
|
||||
///
|
||||
/// Unlike in POSIX, in WASI the bind operation is async. This enables
|
||||
/// interactive WASI hosts to inject permission prompts. Runtimes that
|
||||
/// don't want to make use of this ability can simply call the native
|
||||
/// `bind` as part of either `start-bind` or `finish-bind`.
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/bind.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>
|
||||
@since(version = 0.2.0)
|
||||
start-bind: func(network: borrow<network>, local-address: ip-socket-address) -> result<_, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
finish-bind: func() -> result<_, error-code>;
|
||||
|
||||
/// Connect to a remote endpoint.
|
||||
///
|
||||
/// On success:
|
||||
/// - the socket is transitioned into the `connected` state.
|
||||
/// - a pair of streams is returned that can be used to read & write to the connection
|
||||
///
|
||||
/// After a failed connection attempt, the socket will be in the `closed`
|
||||
/// state and the only valid action left is to `drop` the socket. A single
|
||||
/// socket can not be used to connect more than once.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT)
|
||||
/// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS)
|
||||
/// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos)
|
||||
/// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows)
|
||||
/// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows)
|
||||
/// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`.
|
||||
/// - `invalid-state`: The socket is already in the `connected` state. (EISCONN)
|
||||
/// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows)
|
||||
/// - `timeout`: Connection timed out. (ETIMEDOUT)
|
||||
/// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED)
|
||||
/// - `connection-reset`: The connection was reset. (ECONNRESET)
|
||||
/// - `connection-aborted`: The connection was aborted. (ECONNABORTED)
|
||||
/// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
|
||||
/// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)
|
||||
/// - `not-in-progress`: A connect operation is not in progress.
|
||||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
|
||||
///
|
||||
/// # Implementors note
|
||||
/// The POSIX equivalent of `start-connect` is the regular `connect` syscall.
|
||||
/// Because all WASI sockets are non-blocking this is expected to return
|
||||
/// EINPROGRESS, which should be translated to `ok()` in WASI.
|
||||
///
|
||||
/// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT`
|
||||
/// with a timeout of 0 on the socket descriptor. Followed by a check for
|
||||
/// the `SO_ERROR` socket option, in case the poll signaled readiness.
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/connect.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?connect>
|
||||
@since(version = 0.2.0)
|
||||
start-connect: func(network: borrow<network>, remote-address: ip-socket-address) -> result<_, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
finish-connect: func() -> result<tuple<input-stream, output-stream>, error-code>;
|
||||
|
||||
/// Start listening for new connections.
|
||||
///
|
||||
/// Transitions the socket into the `listening` state.
|
||||
///
|
||||
/// Unlike POSIX, the socket must already be explicitly bound.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ)
|
||||
/// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD)
|
||||
/// - `invalid-state`: The socket is already in the `listening` state.
|
||||
/// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE)
|
||||
/// - `not-in-progress`: A listen operation is not in progress.
|
||||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
|
||||
///
|
||||
/// # Implementors note
|
||||
/// Unlike in POSIX, in WASI the listen operation is async. This enables
|
||||
/// interactive WASI hosts to inject permission prompts. Runtimes that
|
||||
/// don't want to make use of this ability can simply call the native
|
||||
/// `listen` as part of either `start-listen` or `finish-listen`.
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/listen.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=listen&sektion=2>
|
||||
@since(version = 0.2.0)
|
||||
start-listen: func() -> result<_, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
finish-listen: func() -> result<_, error-code>;
|
||||
|
||||
/// Accept a new client socket.
|
||||
///
|
||||
/// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket:
|
||||
/// - `address-family`
|
||||
/// - `keep-alive-enabled`
|
||||
/// - `keep-alive-idle-time`
|
||||
/// - `keep-alive-interval`
|
||||
/// - `keep-alive-count`
|
||||
/// - `hop-limit`
|
||||
/// - `receive-buffer-size`
|
||||
/// - `send-buffer-size`
|
||||
///
|
||||
/// On success, this function returns the newly accepted client socket along with
|
||||
/// a pair of streams that can be used to read & write to the connection.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-state`: Socket is not in the `listening` state. (EINVAL)
|
||||
/// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN)
|
||||
/// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED)
|
||||
/// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/accept.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2>
|
||||
@since(version = 0.2.0)
|
||||
accept: func() -> result<tuple<tcp-socket, input-stream, output-stream>, error-code>;
|
||||
|
||||
/// Get the bound local address.
|
||||
///
|
||||
/// POSIX mentions:
|
||||
/// > If the socket has not been bound to a local name, the value
|
||||
/// > stored in the object pointed to by `address` is unspecified.
|
||||
///
|
||||
/// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-state`: The socket is not bound to any local address.
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?getsockname>
|
||||
@since(version = 0.2.0)
|
||||
local-address: func() -> result<ip-socket-address, error-code>;
|
||||
|
||||
/// Get the remote address.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
|
||||
@since(version = 0.2.0)
|
||||
remote-address: func() -> result<ip-socket-address, error-code>;
|
||||
|
||||
/// Whether the socket is in the `listening` state.
|
||||
///
|
||||
/// Equivalent to the SO_ACCEPTCONN socket option.
|
||||
@since(version = 0.2.0)
|
||||
is-listening: func() -> bool;
|
||||
|
||||
/// Whether this is a IPv4 or IPv6 socket.
|
||||
///
|
||||
/// Equivalent to the SO_DOMAIN socket option.
|
||||
@since(version = 0.2.0)
|
||||
address-family: func() -> ip-address-family;
|
||||
|
||||
/// Hints the desired listen queue size. Implementations are free to ignore this.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
/// Any other value will never cause an error, but it might be silently clamped and/or rounded.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen.
|
||||
/// - `invalid-argument`: (set) The provided value was 0.
|
||||
/// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` state.
|
||||
@since(version = 0.2.0)
|
||||
set-listen-backlog-size: func(value: u64) -> result<_, error-code>;
|
||||
|
||||
/// Enables or disables keepalive.
|
||||
///
|
||||
/// The keepalive behavior can be adjusted using:
|
||||
/// - `keep-alive-idle-time`
|
||||
/// - `keep-alive-interval`
|
||||
/// - `keep-alive-count`
|
||||
/// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true.
|
||||
///
|
||||
/// Equivalent to the SO_KEEPALIVE socket option.
|
||||
@since(version = 0.2.0)
|
||||
keep-alive-enabled: func() -> result<bool, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-keep-alive-enabled: func(value: bool) -> result<_, error-code>;
|
||||
|
||||
/// Amount of time the connection has to be idle before TCP starts sending keepalive packets.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
/// Any other value will never cause an error, but it might be silently clamped and/or rounded.
|
||||
/// I.e. after setting a value, reading the same setting back may return a different value.
|
||||
///
|
||||
/// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS)
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: (set) The provided value was 0.
|
||||
@since(version = 0.2.0)
|
||||
keep-alive-idle-time: func() -> result<duration, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>;
|
||||
|
||||
/// The time between keepalive packets.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
/// Any other value will never cause an error, but it might be silently clamped and/or rounded.
|
||||
/// I.e. after setting a value, reading the same setting back may return a different value.
|
||||
///
|
||||
/// Equivalent to the TCP_KEEPINTVL socket option.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: (set) The provided value was 0.
|
||||
@since(version = 0.2.0)
|
||||
keep-alive-interval: func() -> result<duration, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-keep-alive-interval: func(value: duration) -> result<_, error-code>;
|
||||
|
||||
/// The maximum amount of keepalive packets TCP should send before aborting the connection.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
/// Any other value will never cause an error, but it might be silently clamped and/or rounded.
|
||||
/// I.e. after setting a value, reading the same setting back may return a different value.
|
||||
///
|
||||
/// Equivalent to the TCP_KEEPCNT socket option.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: (set) The provided value was 0.
|
||||
@since(version = 0.2.0)
|
||||
keep-alive-count: func() -> result<u32, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-keep-alive-count: func(value: u32) -> result<_, error-code>;
|
||||
|
||||
/// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: (set) The TTL value must be 1 or higher.
|
||||
@since(version = 0.2.0)
|
||||
hop-limit: func() -> result<u8, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-hop-limit: func(value: u8) -> result<_, error-code>;
|
||||
|
||||
/// The kernel buffer space reserved for sends/receives on this socket.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
/// Any other value will never cause an error, but it might be silently clamped and/or rounded.
|
||||
/// I.e. after setting a value, reading the same setting back may return a different value.
|
||||
///
|
||||
/// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: (set) The provided value was 0.
|
||||
@since(version = 0.2.0)
|
||||
receive-buffer-size: func() -> result<u64, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
send-buffer-size: func() -> result<u64, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-send-buffer-size: func(value: u64) -> result<_, error-code>;
|
||||
|
||||
/// Create a `pollable` which can be used to poll for, or block on,
|
||||
/// completion of any of the asynchronous operations of this socket.
|
||||
///
|
||||
/// When `finish-bind`, `finish-listen`, `finish-connect` or `accept`
|
||||
/// return `error(would-block)`, this pollable can be used to wait for
|
||||
/// their success or failure, after which the method can be retried.
|
||||
///
|
||||
/// The pollable is not limited to the async operation that happens to be
|
||||
/// in progress at the time of calling `subscribe` (if any). Theoretically,
|
||||
/// `subscribe` only has to be called once per socket and can then be
|
||||
/// (re)used for the remainder of the socket's lifetime.
|
||||
///
|
||||
/// See <https://github.com/WebAssembly/wasi-sockets/blob/main/TcpSocketOperationalSemantics.md#pollable-readiness>
|
||||
/// for more information.
|
||||
///
|
||||
/// Note: this function is here for WASI 0.2 only.
|
||||
/// It's planned to be removed when `future` is natively supported in Preview3.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
|
||||
/// Initiate a graceful shutdown.
|
||||
///
|
||||
/// - `receive`: The socket is not expecting to receive any data from
|
||||
/// the peer. The `input-stream` associated with this socket will be
|
||||
/// closed. Any data still in the receive queue at time of calling
|
||||
/// this method will be discarded.
|
||||
/// - `send`: The socket has no more data to send to the peer. The `output-stream`
|
||||
/// associated with this socket will be closed and a FIN packet will be sent.
|
||||
/// - `both`: Same effect as `receive` & `send` combined.
|
||||
///
|
||||
/// This function is idempotent; shutting down a direction more than once
|
||||
/// has no effect and returns `ok`.
|
||||
///
|
||||
/// The shutdown function does not close (drop) the socket.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/shutdown.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=shutdown&sektion=2>
|
||||
@since(version = 0.2.0)
|
||||
shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
@since(version = 0.2.0)
|
||||
interface udp-create-socket {
|
||||
@since(version = 0.2.0)
|
||||
use network.{network, error-code, ip-address-family};
|
||||
@since(version = 0.2.0)
|
||||
use udp.{udp-socket};
|
||||
|
||||
/// Create a new UDP socket.
|
||||
///
|
||||
/// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX.
|
||||
/// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise.
|
||||
///
|
||||
/// This function does not require a network capability handle. This is considered to be safe because
|
||||
/// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called,
|
||||
/// the socket is effectively an in-memory configuration object, unable to communicate with the outside world.
|
||||
///
|
||||
/// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT)
|
||||
/// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE)
|
||||
///
|
||||
/// # References:
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/socket.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>
|
||||
@since(version = 0.2.0)
|
||||
create-udp-socket: func(address-family: ip-address-family) -> result<udp-socket, error-code>;
|
||||
}
|
||||
288
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/udp.wit
Normal file
288
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/udp.wit
Normal file
@@ -0,0 +1,288 @@
|
||||
@since(version = 0.2.0)
|
||||
interface udp {
|
||||
@since(version = 0.2.0)
|
||||
use wasi:io/poll@0.2.6.{pollable};
|
||||
@since(version = 0.2.0)
|
||||
use network.{network, error-code, ip-socket-address, ip-address-family};
|
||||
|
||||
/// A received datagram.
|
||||
@since(version = 0.2.0)
|
||||
record incoming-datagram {
|
||||
/// The payload.
|
||||
///
|
||||
/// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes.
|
||||
data: list<u8>,
|
||||
|
||||
/// The source address.
|
||||
///
|
||||
/// This field is guaranteed to match the remote address the stream was initialized with, if any.
|
||||
///
|
||||
/// Equivalent to the `src_addr` out parameter of `recvfrom`.
|
||||
remote-address: ip-socket-address,
|
||||
}
|
||||
|
||||
/// A datagram to be sent out.
|
||||
@since(version = 0.2.0)
|
||||
record outgoing-datagram {
|
||||
/// The payload.
|
||||
data: list<u8>,
|
||||
|
||||
/// The destination address.
|
||||
///
|
||||
/// The requirements on this field depend on how the stream was initialized:
|
||||
/// - with a remote address: this field must be None or match the stream's remote address exactly.
|
||||
/// - without a remote address: this field is required.
|
||||
///
|
||||
/// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`.
|
||||
remote-address: option<ip-socket-address>,
|
||||
}
|
||||
|
||||
/// A UDP socket handle.
|
||||
@since(version = 0.2.0)
|
||||
resource udp-socket {
|
||||
/// Bind the socket to a specific network on the provided IP address and port.
|
||||
///
|
||||
/// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which
|
||||
/// network interface(s) to bind to.
|
||||
/// If the port is zero, the socket will be bound to a random free port.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)
|
||||
/// - `invalid-state`: The socket is already bound. (EINVAL)
|
||||
/// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows)
|
||||
/// - `address-in-use`: Address is already in use. (EADDRINUSE)
|
||||
/// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL)
|
||||
/// - `not-in-progress`: A `bind` operation is not in progress.
|
||||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
|
||||
///
|
||||
/// # Implementors note
|
||||
/// Unlike in POSIX, in WASI the bind operation is async. This enables
|
||||
/// interactive WASI hosts to inject permission prompts. Runtimes that
|
||||
/// don't want to make use of this ability can simply call the native
|
||||
/// `bind` as part of either `start-bind` or `finish-bind`.
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/bind.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>
|
||||
@since(version = 0.2.0)
|
||||
start-bind: func(network: borrow<network>, local-address: ip-socket-address) -> result<_, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
finish-bind: func() -> result<_, error-code>;
|
||||
|
||||
/// Set up inbound & outbound communication channels, optionally to a specific peer.
|
||||
///
|
||||
/// This function only changes the local socket configuration and does not generate any network traffic.
|
||||
/// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well,
|
||||
/// based on the best network path to `remote-address`.
|
||||
///
|
||||
/// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer:
|
||||
/// - `send` can only be used to send to this destination.
|
||||
/// - `receive` will only return datagrams sent from the provided `remote-address`.
|
||||
///
|
||||
/// This method may be called multiple times on the same socket to change its association, but
|
||||
/// only the most recently returned pair of streams will be operational. Implementations may trap if
|
||||
/// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again.
|
||||
///
|
||||
/// The POSIX equivalent in pseudo-code is:
|
||||
/// ```text
|
||||
/// if (was previously connected) {
|
||||
/// connect(s, AF_UNSPEC)
|
||||
/// }
|
||||
/// if (remote_address is Some) {
|
||||
/// connect(s, remote_address)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Unlike in POSIX, the socket must already be explicitly bound.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT)
|
||||
/// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)
|
||||
/// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)
|
||||
/// - `invalid-state`: The socket is not bound.
|
||||
/// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)
|
||||
/// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
|
||||
/// - `connection-refused`: The connection was refused. (ECONNREFUSED)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/connect.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?connect>
|
||||
@since(version = 0.2.0)
|
||||
%stream: func(remote-address: option<ip-socket-address>) -> result<tuple<incoming-datagram-stream, outgoing-datagram-stream>, error-code>;
|
||||
|
||||
/// Get the current bound address.
|
||||
///
|
||||
/// POSIX mentions:
|
||||
/// > If the socket has not been bound to a local name, the value
|
||||
/// > stored in the object pointed to by `address` is unspecified.
|
||||
///
|
||||
/// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-state`: The socket is not bound to any local address.
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?getsockname>
|
||||
@since(version = 0.2.0)
|
||||
local-address: func() -> result<ip-socket-address, error-code>;
|
||||
|
||||
/// Get the address the socket is currently streaming to.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
|
||||
@since(version = 0.2.0)
|
||||
remote-address: func() -> result<ip-socket-address, error-code>;
|
||||
|
||||
/// Whether this is a IPv4 or IPv6 socket.
|
||||
///
|
||||
/// Equivalent to the SO_DOMAIN socket option.
|
||||
@since(version = 0.2.0)
|
||||
address-family: func() -> ip-address-family;
|
||||
|
||||
/// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: (set) The TTL value must be 1 or higher.
|
||||
@since(version = 0.2.0)
|
||||
unicast-hop-limit: func() -> result<u8, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-unicast-hop-limit: func(value: u8) -> result<_, error-code>;
|
||||
|
||||
/// The kernel buffer space reserved for sends/receives on this socket.
|
||||
///
|
||||
/// If the provided value is 0, an `invalid-argument` error is returned.
|
||||
/// Any other value will never cause an error, but it might be silently clamped and/or rounded.
|
||||
/// I.e. after setting a value, reading the same setting back may return a different value.
|
||||
///
|
||||
/// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: (set) The provided value was 0.
|
||||
@since(version = 0.2.0)
|
||||
receive-buffer-size: func() -> result<u64, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
send-buffer-size: func() -> result<u64, error-code>;
|
||||
@since(version = 0.2.0)
|
||||
set-send-buffer-size: func(value: u64) -> result<_, error-code>;
|
||||
|
||||
/// Create a `pollable` which will resolve once the socket is ready for I/O.
|
||||
///
|
||||
/// Note: this function is here for WASI 0.2 only.
|
||||
/// It's planned to be removed when `future` is natively supported in Preview3.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
resource incoming-datagram-stream {
|
||||
/// Receive messages on the socket.
|
||||
///
|
||||
/// This function attempts to receive up to `max-results` datagrams on the socket without blocking.
|
||||
/// The returned list may contain fewer elements than requested, but never more.
|
||||
///
|
||||
/// This function returns successfully with an empty list when either:
|
||||
/// - `max-results` is 0, or:
|
||||
/// - `max-results` is greater than 0, but no results are immediately available.
|
||||
/// This function never returns `error(would-block)`.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
|
||||
/// - `connection-refused`: The connection was refused. (ECONNREFUSED)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html>
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/recv.2.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/recvmmsg.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom>
|
||||
/// - <https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms741687(v=vs.85)>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=recv&sektion=2>
|
||||
@since(version = 0.2.0)
|
||||
receive: func(max-results: u64) -> result<list<incoming-datagram>, error-code>;
|
||||
|
||||
/// Create a `pollable` which will resolve once the stream is ready to receive again.
|
||||
///
|
||||
/// Note: this function is here for WASI 0.2 only.
|
||||
/// It's planned to be removed when `future` is natively supported in Preview3.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
}
|
||||
|
||||
@since(version = 0.2.0)
|
||||
resource outgoing-datagram-stream {
|
||||
/// Check readiness for sending. This function never blocks.
|
||||
///
|
||||
/// Returns the number of datagrams permitted for the next call to `send`,
|
||||
/// or an error. Calling `send` with more datagrams than this function has
|
||||
/// permitted will trap.
|
||||
///
|
||||
/// When this function returns ok(0), the `subscribe` pollable will
|
||||
/// become ready when this function will report at least ok(1), or an
|
||||
/// error.
|
||||
///
|
||||
/// Never returns `would-block`.
|
||||
check-send: func() -> result<u64, error-code>;
|
||||
|
||||
/// Send messages on the socket.
|
||||
///
|
||||
/// This function attempts to send all provided `datagrams` on the socket without blocking and
|
||||
/// returns how many messages were actually sent (or queued for sending). This function never
|
||||
/// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned.
|
||||
///
|
||||
/// This function semantically behaves the same as iterating the `datagrams` list and sequentially
|
||||
/// sending each individual datagram until either the end of the list has been reached or the first error occurred.
|
||||
/// If at least one datagram has been sent successfully, this function never returns an error.
|
||||
///
|
||||
/// If the input list is empty, the function returns `ok(0)`.
|
||||
///
|
||||
/// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if
|
||||
/// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted.
|
||||
///
|
||||
/// # Typical errors
|
||||
/// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT)
|
||||
/// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)
|
||||
/// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)
|
||||
/// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN)
|
||||
/// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ)
|
||||
/// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
|
||||
/// - `connection-refused`: The connection was refused. (ECONNREFUSED)
|
||||
/// - `datagram-too-large`: The datagram is too large. (EMSGSIZE)
|
||||
///
|
||||
/// # References
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html>
|
||||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/send.2.html>
|
||||
/// - <https://man7.org/linux/man-pages/man2/sendmmsg.2.html>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto>
|
||||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasendmsg>
|
||||
/// - <https://man.freebsd.org/cgi/man.cgi?query=send&sektion=2>
|
||||
@since(version = 0.2.0)
|
||||
send: func(datagrams: list<outgoing-datagram>) -> result<u64, error-code>;
|
||||
|
||||
/// Create a `pollable` which will resolve once the stream is ready to send again.
|
||||
///
|
||||
/// Note: this function is here for WASI 0.2 only.
|
||||
/// It's planned to be removed when `future` is natively supported in Preview3.
|
||||
@since(version = 0.2.0)
|
||||
subscribe: func() -> pollable;
|
||||
}
|
||||
}
|
||||
19
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/world.wit
Normal file
19
crates/wasm-runtime-guest/wit/deps-0.2.6/sockets/world.wit
Normal file
@@ -0,0 +1,19 @@
|
||||
package wasi:sockets@0.2.6;
|
||||
|
||||
@since(version = 0.2.0)
|
||||
world imports {
|
||||
@since(version = 0.2.0)
|
||||
import instance-network;
|
||||
@since(version = 0.2.0)
|
||||
import network;
|
||||
@since(version = 0.2.0)
|
||||
import udp;
|
||||
@since(version = 0.2.0)
|
||||
import udp-create-socket;
|
||||
@since(version = 0.2.0)
|
||||
import tcp;
|
||||
@since(version = 0.2.0)
|
||||
import tcp-create-socket;
|
||||
@since(version = 0.2.0)
|
||||
import ip-name-lookup;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/// A keyvalue interface that provides atomic operations.
|
||||
///
|
||||
/// Atomic operations are single, indivisible operations. When a fault causes an atomic operation to
|
||||
/// fail, it will appear to the invoker of the atomic operation that the action either completed
|
||||
/// successfully or did nothing at all.
|
||||
///
|
||||
/// Please note that this interface is bare functions that take a reference to a bucket. This is to
|
||||
/// get around the current lack of a way to "extend" a resource with additional methods inside of
|
||||
/// wit. Future version of the interface will instead extend these methods on the base `bucket`
|
||||
/// resource.
|
||||
interface atomics {
|
||||
use store.{bucket, error};
|
||||
|
||||
/// Atomically increment the value associated with the key in the store by the given delta. It
|
||||
/// returns the new value.
|
||||
///
|
||||
/// If the key does not exist in the store, it creates a new key-value pair with the value set
|
||||
/// to the given delta.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
increment: func(bucket: borrow<bucket>, key: string, delta: u64) -> result<u64, error>;
|
||||
}
|
||||
63
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/batch.wit
Normal file
63
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/batch.wit
Normal file
@@ -0,0 +1,63 @@
|
||||
/// A keyvalue interface that provides batch operations.
|
||||
///
|
||||
/// A batch operation is an operation that operates on multiple keys at once.
|
||||
///
|
||||
/// Batch operations are useful for reducing network round-trip time. For example, if you want to
|
||||
/// get the values associated with 100 keys, you can either do 100 get operations or you can do 1
|
||||
/// batch get operation. The batch operation is faster because it only needs to make 1 network call
|
||||
/// instead of 100.
|
||||
///
|
||||
/// A batch operation does not guarantee atomicity, meaning that if the batch operation fails, some
|
||||
/// of the keys may have been modified and some may not.
|
||||
///
|
||||
/// This interface does has the same consistency guarantees as the `store` interface, meaning that
|
||||
/// you should be able to "read your writes."
|
||||
///
|
||||
/// Please note that this interface is bare functions that take a reference to a bucket. This is to
|
||||
/// get around the current lack of a way to "extend" a resource with additional methods inside of
|
||||
/// wit. Future version of the interface will instead extend these methods on the base `bucket`
|
||||
/// resource.
|
||||
interface batch {
|
||||
use store.{bucket, error};
|
||||
|
||||
/// Get the key-value pairs associated with the keys in the store. It returns a list of
|
||||
/// key-value pairs.
|
||||
///
|
||||
/// If any of the keys do not exist in the store, it returns a `none` value for that pair in the
|
||||
/// list.
|
||||
///
|
||||
/// MAY show an out-of-date value if there are concurrent writes to the store.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
get-many: func(bucket: borrow<bucket>, keys: list<string>) -> result<list<option<tuple<string, list<u8>>>>, error>;
|
||||
|
||||
/// Set the values associated with the keys in the store. If the key already exists in the
|
||||
/// store, it overwrites the value.
|
||||
///
|
||||
/// Note that the key-value pairs are not guaranteed to be set in the order they are provided.
|
||||
///
|
||||
/// If any of the keys do not exist in the store, it creates a new key-value pair.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not
|
||||
/// rollback the key-value pairs that were already set. Thus, this batch operation does not
|
||||
/// guarantee atomicity, implying that some key-value pairs could be set while others might
|
||||
/// fail.
|
||||
///
|
||||
/// Other concurrent operations may also be able to see the partial results.
|
||||
set-many: func(bucket: borrow<bucket>, key-values: list<tuple<string, list<u8>>>) -> result<_, error>;
|
||||
|
||||
/// Delete the key-value pairs associated with the keys in the store.
|
||||
///
|
||||
/// Note that the key-value pairs are not guaranteed to be deleted in the order they are
|
||||
/// provided.
|
||||
///
|
||||
/// If any of the keys do not exist in the store, it skips the key.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not
|
||||
/// rollback the key-value pairs that were already deleted. Thus, this batch operation does not
|
||||
/// guarantee atomicity, implying that some key-value pairs could be deleted while others might
|
||||
/// fail.
|
||||
///
|
||||
/// Other concurrent operations may also be able to see the partial results.
|
||||
delete-many: func(bucket: borrow<bucket>, keys: list<string>) -> result<_, error>;
|
||||
}
|
||||
122
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/store.wit
Normal file
122
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/store.wit
Normal file
@@ -0,0 +1,122 @@
|
||||
/// A keyvalue interface that provides eventually consistent key-value operations.
|
||||
///
|
||||
/// Each of these operations acts on a single key-value pair.
|
||||
///
|
||||
/// The value in the key-value pair is defined as a `u8` byte array and the intention is that it is
|
||||
/// the common denominator for all data types defined by different key-value stores to handle data,
|
||||
/// ensuring compatibility between different key-value stores. Note: the clients will be expecting
|
||||
/// serialization/deserialization overhead to be handled by the key-value store. The value could be
|
||||
/// a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects.
|
||||
///
|
||||
/// Data consistency in a key value store refers to the guarantee that once a write operation
|
||||
/// completes, all subsequent read operations will return the value that was written.
|
||||
///
|
||||
/// Any implementation of this interface must have enough consistency to guarantee "reading your
|
||||
/// writes." In particular, this means that the client should never get a value that is older than
|
||||
/// the one it wrote, but it MAY get a newer value if one was written around the same time. These
|
||||
/// guarantees only apply to the same client (which will likely be provided by the host or an
|
||||
/// external capability of some kind). In this context a "client" is referring to the caller or
|
||||
/// guest that is consuming this interface. Once a write request is committed by a specific client,
|
||||
/// all subsequent read requests by the same client will reflect that write or any subsequent
|
||||
/// writes. Another client running in a different context may or may not immediately see the result
|
||||
/// due to the replication lag. As an example of all of this, if a value at a given key is A, and
|
||||
/// the client writes B, then immediately reads, it should get B. If something else writes C in
|
||||
/// quick succession, then the client may get C. However, a client running in a separate context may
|
||||
/// still see A or B
|
||||
interface store {
|
||||
/// The set of errors which may be raised by functions in this package
|
||||
variant error {
|
||||
/// The host does not recognize the store identifier requested.
|
||||
no-such-store,
|
||||
|
||||
/// The requesting component does not have access to the specified store
|
||||
/// (which may or may not exist).
|
||||
access-denied,
|
||||
|
||||
/// Some implementation-specific error has occurred (e.g. I/O)
|
||||
other(string)
|
||||
}
|
||||
|
||||
/// A response to a `list-keys` operation.
|
||||
record key-response {
|
||||
/// The list of keys returned by the query.
|
||||
keys: list<string>,
|
||||
/// The continuation token to use to fetch the next page of keys. If this is `null`, then
|
||||
/// there are no more keys to fetch.
|
||||
cursor: option<u64>
|
||||
}
|
||||
|
||||
/// Get the bucket with the specified identifier.
|
||||
///
|
||||
/// `identifier` must refer to a bucket provided by the host.
|
||||
///
|
||||
/// `error::no-such-store` will be raised if the `identifier` is not recognized.
|
||||
open: func(identifier: string) -> result<bucket, error>;
|
||||
|
||||
/// A bucket is a collection of key-value pairs. Each key-value pair is stored as a entry in the
|
||||
/// bucket, and the bucket itself acts as a collection of all these entries.
|
||||
///
|
||||
/// It is worth noting that the exact terminology for bucket in key-value stores can very
|
||||
/// depending on the specific implementation. For example:
|
||||
///
|
||||
/// 1. Amazon DynamoDB calls a collection of key-value pairs a table
|
||||
/// 2. Redis has hashes, sets, and sorted sets as different types of collections
|
||||
/// 3. Cassandra calls a collection of key-value pairs a column family
|
||||
/// 4. MongoDB calls a collection of key-value pairs a collection
|
||||
/// 5. Riak calls a collection of key-value pairs a bucket
|
||||
/// 6. Memcached calls a collection of key-value pairs a slab
|
||||
/// 7. Azure Cosmos DB calls a collection of key-value pairs a container
|
||||
///
|
||||
/// In this interface, we use the term `bucket` to refer to a collection of key-value pairs
|
||||
resource bucket {
|
||||
/// Get the value associated with the specified `key`
|
||||
///
|
||||
/// The value is returned as an option. If the key-value pair exists in the
|
||||
/// store, it returns `Ok(value)`. If the key does not exist in the
|
||||
/// store, it returns `Ok(none)`.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
get: func(key: string) -> result<option<list<u8>>, error>;
|
||||
|
||||
/// Set the value associated with the key in the store. If the key already
|
||||
/// exists in the store, it overwrites the value.
|
||||
///
|
||||
/// If the key does not exist in the store, it creates a new key-value pair.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
set: func(key: string, value: list<u8>) -> result<_, error>;
|
||||
|
||||
/// Delete the key-value pair associated with the key in the store.
|
||||
///
|
||||
/// If the key does not exist in the store, it does nothing.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
delete: func(key: string) -> result<_, error>;
|
||||
|
||||
/// Check if the key exists in the store.
|
||||
///
|
||||
/// If the key exists in the store, it returns `Ok(true)`. If the key does
|
||||
/// not exist in the store, it returns `Ok(false)`.
|
||||
///
|
||||
/// If any other error occurs, it returns an `Err(error)`.
|
||||
exists: func(key: string) -> result<bool, error>;
|
||||
|
||||
/// Get all the keys in the store with an optional cursor (for use in pagination). It
|
||||
/// returns a list of keys. Please note that for most KeyValue implementations, this is a
|
||||
/// can be a very expensive operation and so it should be used judiciously. Implementations
|
||||
/// can return any number of keys in a single response, but they should never attempt to
|
||||
/// send more data than is reasonable (i.e. on a small edge device, this may only be a few
|
||||
/// KB, while on a large machine this could be several MB). Any response should also return
|
||||
/// a cursor that can be used to fetch the next page of keys. See the `key-response` record
|
||||
/// for more information.
|
||||
///
|
||||
/// Note that the keys are not guaranteed to be returned in any particular order.
|
||||
///
|
||||
/// If the store is empty, it returns an empty list.
|
||||
///
|
||||
/// MAY show an out-of-date list of keys if there are concurrent writes to the store.
|
||||
///
|
||||
/// If any error occurs, it returns an `Err(error)`.
|
||||
list-keys: func(cursor: option<u64>) -> result<key-response, error>;
|
||||
}
|
||||
}
|
||||
16
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/watch.wit
Normal file
16
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/watch.wit
Normal file
@@ -0,0 +1,16 @@
|
||||
/// A keyvalue interface that provides watch operations.
|
||||
///
|
||||
/// This interface is used to provide event-driven mechanisms to handle
|
||||
/// keyvalue changes.
|
||||
interface watcher {
|
||||
/// A keyvalue interface that provides handle-watch operations.
|
||||
use store.{bucket};
|
||||
|
||||
/// Handle the `set` event for the given bucket and key. It includes a reference to the `bucket`
|
||||
/// that can be used to interact with the store.
|
||||
on-set: func(bucket: bucket, key: string, value: list<u8>);
|
||||
|
||||
/// Handle the `delete` event for the given bucket and key. It includes a reference to the
|
||||
/// `bucket` that can be used to interact with the store.
|
||||
on-delete: func(bucket: bucket, key: string);
|
||||
}
|
||||
26
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/world.wit
Normal file
26
crates/wasm-runtime-guest/wit/keyvalue-0.2.0-draft/world.wit
Normal file
@@ -0,0 +1,26 @@
|
||||
package wasi:keyvalue@0.2.0-draft;
|
||||
|
||||
/// The `wasi:keyvalue/imports` world provides common APIs for interacting with key-value stores.
|
||||
/// Components targeting this world will be able to do:
|
||||
///
|
||||
/// 1. CRUD (create, read, update, delete) operations on key-value stores.
|
||||
/// 2. Atomic `increment` and CAS (compare-and-swap) operations.
|
||||
/// 3. Batch operations that can reduce the number of round trips to the network.
|
||||
world imports {
|
||||
/// The `store` capability allows the component to perform eventually consistent operations on
|
||||
/// the key-value store.
|
||||
import store;
|
||||
|
||||
/// The `atomic` capability allows the component to perform atomic / `increment` and CAS
|
||||
/// (compare-and-swap) operations.
|
||||
import atomics;
|
||||
|
||||
/// The `batch` capability allows the component to perform eventually consistent batch
|
||||
/// operations that can reduce the number of round trips to the network.
|
||||
import batch;
|
||||
}
|
||||
|
||||
world watch-service {
|
||||
include imports;
|
||||
export watcher;
|
||||
}
|
||||
78
crates/wasm-runtime-guest/wit/trailbase.wit
Normal file
78
crates/wasm-runtime-guest/wit/trailbase.wit
Normal file
@@ -0,0 +1,78 @@
|
||||
package trailbase:runtime;
|
||||
|
||||
interface init-endpoint {
|
||||
enum method-type {
|
||||
get,
|
||||
post,
|
||||
head,
|
||||
options,
|
||||
patch,
|
||||
delete,
|
||||
put,
|
||||
trace,
|
||||
connect,
|
||||
}
|
||||
|
||||
record init-result {
|
||||
/// Registered http handlers (method, path)[].
|
||||
http-handlers: list<tuple<method-type, string>>,
|
||||
|
||||
/// Registered jobs (name, spec)[].
|
||||
job-handlers: list<tuple<string, string>>,
|
||||
}
|
||||
|
||||
init: func() -> init-result;
|
||||
}
|
||||
|
||||
interface host-endpoint {
|
||||
thread-id: func() -> u64;
|
||||
|
||||
|
||||
variant tx-error {
|
||||
other(string)
|
||||
}
|
||||
|
||||
variant value {
|
||||
null,
|
||||
text(string),
|
||||
blob(list<u8>),
|
||||
integer(s64),
|
||||
real(f64),
|
||||
}
|
||||
|
||||
// NOTE: Ideally, we'd use these but they currently block guests.
|
||||
execute: func(query: string, params: list<value>) -> result<u64, tx-error>;
|
||||
query: func(query: string, params: list<value>) -> result<list<list<value>>, tx-error>;
|
||||
|
||||
// However, transactions have to be sync.
|
||||
tx-begin: func() -> result<_, tx-error>;
|
||||
tx-commit: func() -> result<_, tx-error>;
|
||||
tx-rollback: func() -> result<_, tx-error>;
|
||||
|
||||
tx-execute: func(query: string, params: list<value>) -> result<u64, tx-error>;
|
||||
tx-query: func(query: string, params: list<value>) -> result<list<list<value>>, tx-error>;
|
||||
}
|
||||
|
||||
// Note:
|
||||
// * imports are provided by the host
|
||||
// * exports are provided by the guest
|
||||
// * includes to include a world into another world.
|
||||
world trailbase {
|
||||
// Pull in WASIp2 http interface for outbound requests.
|
||||
import wasi:http/outgoing-handler@0.2.6;
|
||||
|
||||
// Pull in WASIp2 filesystem interfaces.
|
||||
include wasi:filesystem/imports@0.2.6;
|
||||
|
||||
// Pull in WASI random interfaces.
|
||||
include wasi:random/imports@0.2.6;
|
||||
|
||||
// Pull in WSAI Key-Value interfaces.
|
||||
include wasi:keyvalue/imports@0.2.0-draft;
|
||||
|
||||
// Host-provided interfaces.
|
||||
import host-endpoint;
|
||||
|
||||
// Guest-provided interfaces.
|
||||
export init-endpoint;
|
||||
}
|
||||
36
crates/wasm-runtime-host/Cargo.toml
Normal file
36
crates/wasm-runtime-host/Cargo.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "trailbase-wasm-runtime-host"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "OSL-3.0"
|
||||
description = "WASM runtime for the TrailBase framework"
|
||||
homepage = "https://trailbase.io"
|
||||
exclude = [
|
||||
"**/node_modules/",
|
||||
"**/dist/",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.10.1"
|
||||
futures-util = "0.3.31"
|
||||
http = "1.3.1"
|
||||
http-body-util = "0.1.3"
|
||||
hyper = "1.6.0"
|
||||
kanal = "0.1.1"
|
||||
log = { version = "^0.4.21", default-features = false }
|
||||
parking_lot = { workspace = true }
|
||||
rusqlite = { workspace = true }
|
||||
self_cell = "1.2.0"
|
||||
serde = { version = "^1.0.203", features = ["derive"] }
|
||||
serde_json = "^1.0.117"
|
||||
thiserror = "2.0.14"
|
||||
trailbase-schema = { workspace = true }
|
||||
trailbase-sqlite = { workspace = true }
|
||||
trailbase-wasm-common = { workspace = true }
|
||||
trailbase-wasi-keyvalue = { workspace = true }
|
||||
tokio = { version = "^1.38.0", features = ["macros", "rt-multi-thread"] }
|
||||
tracing = "0.1.41"
|
||||
wasmtime = { workspace = true }
|
||||
wasmtime-wasi = { workspace = true }
|
||||
wasmtime-wasi-http = { workspace = true }
|
||||
wasmtime-wasi-io = { workspace = true }
|
||||
896
crates/wasm-runtime-host/src/lib.rs
Normal file
896
crates/wasm-runtime-host/src/lib.rs
Normal file
@@ -0,0 +1,896 @@
|
||||
#![forbid(clippy::unwrap_used)]
|
||||
#![allow(clippy::needless_return)]
|
||||
#![warn(clippy::await_holding_lock, clippy::inefficient_to_string)]
|
||||
|
||||
mod sqlite;
|
||||
|
||||
use bytes::Bytes;
|
||||
use core::future::Future;
|
||||
use futures_util::TryFutureExt;
|
||||
use futures_util::future::LocalBoxFuture;
|
||||
use http_body_util::combinators::BoxBody;
|
||||
use parking_lot::Mutex;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::SystemTime;
|
||||
use trailbase::runtime::host_endpoint::{TxError, Value};
|
||||
use trailbase_sqlite::{Params, Rows};
|
||||
use trailbase_wasi_keyvalue::WasiKeyValueCtx;
|
||||
use wasmtime::component::{Component, HasSelf, Linker, ResourceTable};
|
||||
use wasmtime::{Config, Engine, Result, Store};
|
||||
use wasmtime_wasi::p2::add_to_linker_async;
|
||||
use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxView};
|
||||
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
|
||||
use wasmtime_wasi_http::bindings::http::types::ErrorCode;
|
||||
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
|
||||
use wasmtime_wasi_io::IoView;
|
||||
|
||||
use crate::exports::trailbase::runtime::init_endpoint::InitResult;
|
||||
|
||||
pub use trailbase_wasi_keyvalue::Store as KvStore;
|
||||
|
||||
static IN_FLIGHT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
// Documentation: https://docs.wasmtime.dev/api/wasmtime/component/macro.bindgen.html
|
||||
wasmtime::component::bindgen!({
|
||||
world: "trailbase:runtime/trailbase",
|
||||
path: [
|
||||
// Order-sensitive: will import *.wit from the folder.
|
||||
"wit/deps-0.2.6/random",
|
||||
"wit/deps-0.2.6/io",
|
||||
"wit/deps-0.2.6/clocks",
|
||||
"wit/deps-0.2.6/filesystem",
|
||||
"wit/deps-0.2.6/sockets",
|
||||
"wit/deps-0.2.6/cli",
|
||||
"wit/deps-0.2.6/http",
|
||||
"wit/keyvalue-0.2.0-draft",
|
||||
// Ours:
|
||||
"wit/trailbase.wit",
|
||||
],
|
||||
// NOTE: This doesn't seem to work even though it should be fixed:
|
||||
// https://github.com/bytecodealliance/wasmtime/issues/10677
|
||||
// i.e. can't add db locks to shared state.
|
||||
require_store_data_send: false,
|
||||
// NOTE: Doesn't work: https://github.com/bytecodealliance/wit-bindgen/issues/812.
|
||||
// additional_derives: [
|
||||
// serde::Deserialize,
|
||||
// serde::Serialize,
|
||||
// ],
|
||||
// Interactions with `ResourceTable` can possibly trap so enable the ability
|
||||
// to return traps from generated functions.
|
||||
imports: {
|
||||
"trailbase:runtime/host-endpoint/tx-commit": trappable,
|
||||
"trailbase:runtime/host-endpoint/tx-rollback": trappable,
|
||||
"trailbase:runtime/host-endpoint/tx-execute": trappable,
|
||||
"trailbase:runtime/host-endpoint/tx-query": trappable,
|
||||
"trailbase:runtime/host-endpoint/thread-id": trappable,
|
||||
default: async | trappable,
|
||||
},
|
||||
exports: {
|
||||
default: async,
|
||||
},
|
||||
});
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Wasmtime: {0}")]
|
||||
Wasmtime(#[from] wasmtime::Error),
|
||||
#[error("Channel closed")]
|
||||
ChannelClosed,
|
||||
#[error("Http Error: {0}")]
|
||||
HttpErrorCode(ErrorCode),
|
||||
#[error("Encoding")]
|
||||
Encoding,
|
||||
#[error("Other: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
Run(Box<dyn FnOnce(Rc<RuntimeInstance>) -> LocalBoxFuture<'static, ()> + Send>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LockedTransaction(Rc<Mutex<Option<sqlite::OwnedTx>>>);
|
||||
|
||||
unsafe impl Send for LockedTransaction {}
|
||||
|
||||
struct State {
|
||||
resource_table: ResourceTable,
|
||||
wasi_ctx: WasiCtx,
|
||||
http: WasiHttpCtx,
|
||||
kv: WasiKeyValueCtx,
|
||||
|
||||
shared: Arc<SharedState>,
|
||||
tx: LockedTransaction,
|
||||
}
|
||||
|
||||
impl Drop for State {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(debug_assertions)]
|
||||
if self.tx.0.lock().is_some() {
|
||||
log::warn!("pending transaction locking the DB");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IoView for State {
|
||||
fn table(&mut self) -> &mut ResourceTable {
|
||||
return &mut self.resource_table;
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiView for State {
|
||||
fn ctx(&mut self) -> WasiCtxView<'_> {
|
||||
return WasiCtxView {
|
||||
ctx: &mut self.wasi_ctx,
|
||||
table: &mut self.resource_table,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl WasiHttpView for State {
|
||||
fn ctx(&mut self) -> &mut WasiHttpCtx {
|
||||
return &mut self.http;
|
||||
}
|
||||
|
||||
fn table(&mut self) -> &mut ResourceTable {
|
||||
return &mut self.resource_table;
|
||||
}
|
||||
|
||||
/// Receives HTTP fetches from the guest.
|
||||
///
|
||||
/// Based on `WasiView`' default implementation.
|
||||
fn send_request(
|
||||
&mut self,
|
||||
request: hyper::Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
|
||||
config: wasmtime_wasi_http::types::OutgoingRequestConfig,
|
||||
) -> wasmtime_wasi_http::HttpResult<wasmtime_wasi_http::types::HostFutureIncomingResponse> {
|
||||
// log::debug!(
|
||||
// "send_request {:?} {}: {request:?}",
|
||||
// request.uri().host(),
|
||||
// request.uri().path()
|
||||
// );
|
||||
|
||||
return match request.uri().host() {
|
||||
Some("__sqlite") => {
|
||||
let conn = self.shared.conn.clone();
|
||||
Ok(
|
||||
wasmtime_wasi_http::types::HostFutureIncomingResponse::pending(
|
||||
wasmtime_wasi::runtime::spawn(async move {
|
||||
Ok(sqlite::handle_sqlite_request(conn, request).await)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let handle = wasmtime_wasi::runtime::spawn(async move {
|
||||
Ok(wasmtime_wasi_http::types::default_send_request_handler(request, config).await)
|
||||
});
|
||||
Ok(wasmtime_wasi_http::types::HostFutureIncomingResponse::pending(handle))
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl trailbase::runtime::host_endpoint::Host for State {
|
||||
fn thread_id(&mut self) -> wasmtime::Result<u64> {
|
||||
return Ok(self.shared.thread_id);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
query: String,
|
||||
params: Vec<Value>,
|
||||
) -> impl Future<Output = wasmtime::Result<Result<u64, TxError>>> + Send {
|
||||
let conn = self.shared.conn.clone();
|
||||
let params: Vec<_> = params.into_iter().map(to_sqlite_value).collect();
|
||||
|
||||
return self
|
||||
.shared
|
||||
.runtime
|
||||
.spawn(async move {
|
||||
conn
|
||||
.execute(query, params)
|
||||
.await
|
||||
.map_err(|err| TxError::Other(err.to_string()))
|
||||
.map(|v| v as u64)
|
||||
})
|
||||
.map_err(|err| wasmtime::Error::msg(err.to_string()));
|
||||
}
|
||||
|
||||
fn query(
|
||||
&mut self,
|
||||
query: String,
|
||||
params: Vec<Value>,
|
||||
) -> impl Future<Output = wasmtime::Result<Result<Vec<Vec<Value>>, TxError>>> + Send {
|
||||
let conn = self.shared.conn.clone();
|
||||
let params: Vec<_> = params.into_iter().map(to_sqlite_value).collect();
|
||||
|
||||
return self
|
||||
.shared
|
||||
.runtime
|
||||
.spawn(async move {
|
||||
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();
|
||||
|
||||
Ok(values)
|
||||
})
|
||||
.map_err(|err| wasmtime::Error::msg(err.to_string()));
|
||||
}
|
||||
|
||||
fn tx_begin(&mut self) -> impl Future<Output = wasmtime::Result<Result<(), TxError>>> + Send {
|
||||
async fn begin(
|
||||
conn: trailbase_sqlite::Connection,
|
||||
tx: LockedTransaction,
|
||||
) -> Result<(), TxError> {
|
||||
assert!(tx.0.lock().is_none());
|
||||
|
||||
*tx.0.lock() = Some(
|
||||
sqlite::new_tx(conn)
|
||||
.await
|
||||
.map_err(|err| TxError::Other(err.to_string()))?,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tx = self.tx.clone();
|
||||
return self
|
||||
.shared
|
||||
.runtime
|
||||
.spawn(begin(self.shared.conn.clone(), tx))
|
||||
.map_err(|err| wasmtime::Error::msg(err.to_string()));
|
||||
}
|
||||
|
||||
fn tx_commit(&mut self) -> wasmtime::Result<Result<(), TxError>> {
|
||||
fn commit(tx: LockedTransaction) -> Result<(), TxError> {
|
||||
let Some(tx) = tx.0.lock().take() else {
|
||||
return Err(TxError::Other("no pending tx".to_string()));
|
||||
};
|
||||
|
||||
// NOTE: this is the same as `tx.commit()` just w/o consuming.
|
||||
let lock = tx.borrow_dependent();
|
||||
lock
|
||||
.execute_batch("COMMIT")
|
||||
.map_err(|err| TxError::Other(err.to_string()))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Ok(commit(self.tx.clone()));
|
||||
}
|
||||
|
||||
fn tx_rollback(&mut self) -> wasmtime::Result<Result<(), TxError>> {
|
||||
fn rollback(tx: LockedTransaction) -> Result<(), TxError> {
|
||||
let Some(tx) = tx.0.lock().take() else {
|
||||
return Err(TxError::Other("no pending tx".to_string()));
|
||||
};
|
||||
|
||||
// NOTE: this is the same as `tx.rollback()` just w/o consuming.
|
||||
let lock = tx.borrow_dependent();
|
||||
lock
|
||||
.execute_batch("ROLLBACK")
|
||||
.map_err(|err| TxError::Other(err.to_string()))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Ok(rollback(self.tx.clone()));
|
||||
}
|
||||
|
||||
fn tx_execute(
|
||||
&mut self,
|
||||
query: String,
|
||||
params: Vec<Value>,
|
||||
) -> wasmtime::Result<Result<u64, TxError>> {
|
||||
fn execute(tx: LockedTransaction, query: String, params: Vec<Value>) -> Result<u64, TxError> {
|
||||
let params: Vec<_> = params.into_iter().map(to_sqlite_value).collect();
|
||||
|
||||
let Some(ref tx) = *tx.0.lock() else {
|
||||
return Err(TxError::Other("No open transaction".to_string()));
|
||||
};
|
||||
|
||||
let lock = tx.borrow_dependent();
|
||||
let mut stmt = lock
|
||||
.prepare(&query)
|
||||
.map_err(|err| TxError::Other(err.to_string()))?;
|
||||
|
||||
params
|
||||
.bind(&mut stmt)
|
||||
.map_err(|err| TxError::Other(err.to_string()))?;
|
||||
|
||||
return Ok(
|
||||
stmt
|
||||
.raw_execute()
|
||||
.map_err(|err| TxError::Other(err.to_string()))? as u64,
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(execute(self.tx.clone(), query, params));
|
||||
}
|
||||
|
||||
fn tx_query(
|
||||
&mut self,
|
||||
query: String,
|
||||
params: Vec<Value>,
|
||||
) -> wasmtime::Result<Result<Vec<Vec<Value>>, TxError>> {
|
||||
fn query_fn(
|
||||
tx: LockedTransaction,
|
||||
query: String,
|
||||
params: Vec<Value>,
|
||||
) -> Result<Vec<Vec<Value>>, TxError> {
|
||||
let params: Vec<_> = params.into_iter().map(to_sqlite_value).collect();
|
||||
|
||||
let Some(ref tx) = *tx.0.lock() else {
|
||||
return Err(TxError::Other("No open transaction".to_string()));
|
||||
};
|
||||
|
||||
let lock = tx.borrow_dependent();
|
||||
let mut stmt = lock
|
||||
.prepare(&query)
|
||||
.map_err(|err| TxError::Other(err.to_string()))?;
|
||||
|
||||
params
|
||||
.bind(&mut stmt)
|
||||
.map_err(|err| TxError::Other(err.to_string()))?;
|
||||
|
||||
let rows =
|
||||
Rows::from_rows(stmt.raw_query()).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);
|
||||
}
|
||||
|
||||
return Ok(query_fn(self.tx.clone(), query, params));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Runtime {
|
||||
// Shared sender.
|
||||
shared_sender: kanal::AsyncSender<Message>,
|
||||
threads: Vec<(std::thread::JoinHandle<()>, kanal::AsyncSender<Message>)>,
|
||||
}
|
||||
|
||||
impl Drop for Runtime {
|
||||
fn drop(&mut self) {
|
||||
for (handle, ch) in std::mem::take(&mut self.threads) {
|
||||
// Dropping the private channel will trigger the event_loop to return.
|
||||
drop(ch);
|
||||
|
||||
if let Err(err) = handle.join() {
|
||||
log::error!("Failed to join main rt thread: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_config(cache: Option<wasmtime::Cache>) -> Config {
|
||||
let mut config = Config::new();
|
||||
|
||||
// Execution settings.
|
||||
config.async_support(true);
|
||||
config.epoch_interruption(false);
|
||||
config.memory_reservation(64 * 1024 * 1024 /* bytes */);
|
||||
// config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
|
||||
|
||||
// Compilation settings.
|
||||
config.cache(cache);
|
||||
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
||||
config.parallel_compilation(true);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub fn new(
|
||||
n_threads: usize,
|
||||
wasm_source_file: std::path::PathBuf,
|
||||
conn: trailbase_sqlite::Connection,
|
||||
kv_store: KvStore,
|
||||
fs_root_path: Option<std::path::PathBuf>,
|
||||
) -> Result<Self, Error> {
|
||||
let engine = Engine::new(&build_config(Some(wasmtime::Cache::new(
|
||||
wasmtime::CacheConfig::default(),
|
||||
)?)))?;
|
||||
|
||||
// Load the component - a very expensive operation generating code. Compilation happens in
|
||||
// parallel and will saturate the entire machine.
|
||||
let component = {
|
||||
log::info!("Compiling: {wasm_source_file:?}. May take some time...");
|
||||
|
||||
let start = SystemTime::now();
|
||||
let component = wasmtime::CodeBuilder::new(&engine)
|
||||
.wasm_binary_or_text_file(&wasm_source_file)?
|
||||
.compile_component()?;
|
||||
|
||||
// NOTE: According to docs, this shouldn't do anything.
|
||||
component.initialize_copy_on_write_image()?;
|
||||
|
||||
if let Ok(elapsed) = SystemTime::now().duration_since(start) {
|
||||
log::info!("Loaded component {wasm_source_file:?} in: {elapsed:?}.");
|
||||
}
|
||||
component
|
||||
};
|
||||
|
||||
let linker = {
|
||||
let mut linker = Linker::<State>::new(&engine);
|
||||
|
||||
// Adds all the default WASI implementations: clocks, random, fs, ...
|
||||
add_to_linker_async(&mut linker)?;
|
||||
|
||||
// Adds default HTTP interfaces - incoming and outgoing.
|
||||
wasmtime_wasi_http::add_only_http_to_linker_async(&mut linker)?;
|
||||
|
||||
// Add default KV interfaces.
|
||||
trailbase_wasi_keyvalue::add_to_linker(&mut linker, |cx| {
|
||||
trailbase_wasi_keyvalue::WasiKeyValue::new(&cx.kv, &mut cx.resource_table)
|
||||
})?;
|
||||
|
||||
// Host interfaces.
|
||||
trailbase::runtime::host_endpoint::add_to_linker::<_, HasSelf<State>>(&mut linker, |s| s)?;
|
||||
|
||||
linker
|
||||
};
|
||||
|
||||
log::info!("Starting WASM runtime with {n_threads} threads.");
|
||||
|
||||
let (shared_sender, shared_receiver) = kanal::unbounded_async::<Message>();
|
||||
let threads = (0..n_threads)
|
||||
.map(|index| -> Result<_, Error> {
|
||||
let (private_sender, private_receiver) = kanal::unbounded_async::<Message>();
|
||||
|
||||
let shared_receiver = shared_receiver.clone();
|
||||
|
||||
let engine = engine.clone();
|
||||
let component = component.clone();
|
||||
let linker = linker.clone();
|
||||
|
||||
let conn = conn.clone();
|
||||
let kv_store = kv_store.clone();
|
||||
let fs_root_path = fs_root_path.clone();
|
||||
|
||||
let handle = std::thread::Builder::new()
|
||||
.name(format!("wasm-runtime-{index}"))
|
||||
.spawn(move || {
|
||||
// Note: Arc rather than Rc, since State and thus SharedState needs to be Send + Sync.
|
||||
let tokio_runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.enable_io()
|
||||
.build()
|
||||
.expect("startup");
|
||||
|
||||
let shared_state = Arc::new(SharedState {
|
||||
runtime: tokio_runtime,
|
||||
conn,
|
||||
thread_id: index as u64,
|
||||
kv_store,
|
||||
fs_root_path,
|
||||
});
|
||||
|
||||
let instance = RuntimeInstance {
|
||||
engine,
|
||||
component,
|
||||
linker,
|
||||
shared: shared_state.clone(),
|
||||
};
|
||||
// RuntimeInstance::new(engine, component, linker, shared_state).expect("startup");
|
||||
|
||||
event_loop(shared_state, instance, private_receiver, shared_receiver);
|
||||
})
|
||||
.expect("failed to spawn thread");
|
||||
|
||||
return Ok((handle, private_sender));
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
return Ok(Self {
|
||||
shared_sender,
|
||||
threads,
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn call<O, F>(&self, f: F) -> Result<O, Error>
|
||||
where
|
||||
F: (AsyncFnOnce(&RuntimeInstance) -> O) + Send + 'static,
|
||||
O: Send + 'static,
|
||||
{
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel::<O>();
|
||||
|
||||
self
|
||||
.shared_sender
|
||||
.send(Message::Run(Box::new(move |runtime| {
|
||||
Box::pin(async move {
|
||||
let _ = sender.send(f(&*runtime).await);
|
||||
})
|
||||
})))
|
||||
.await
|
||||
.map_err(|_| Error::ChannelClosed)?;
|
||||
|
||||
return receiver.await.map_err(|_| Error::ChannelClosed);
|
||||
}
|
||||
}
|
||||
|
||||
fn event_loop(
|
||||
shared_state: Arc<SharedState>,
|
||||
instance: RuntimeInstance,
|
||||
private_recv: kanal::AsyncReceiver<Message>,
|
||||
shared_recv: kanal::AsyncReceiver<Message>,
|
||||
) {
|
||||
let thread_id = shared_state.thread_id;
|
||||
let local = tokio::task::LocalSet::new();
|
||||
let instance = Rc::new(instance);
|
||||
|
||||
local.block_on(&shared_state.runtime, async move {
|
||||
let local_in_flight = Rc::new(AtomicUsize::new(0));
|
||||
|
||||
loop {
|
||||
let receive_message = async || {
|
||||
return tokio::select! {
|
||||
msg = private_recv.recv() => msg,
|
||||
msg = shared_recv.recv() => msg,
|
||||
};
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"Waiting for new messages (thread: {thread_id}). In flight: {}, {}",
|
||||
local_in_flight.load(Ordering::Relaxed),
|
||||
IN_FLIGHT.load(Ordering::Relaxed)
|
||||
);
|
||||
|
||||
match receive_message().await {
|
||||
Ok(Message::Run(f)) => {
|
||||
let instance = instance.clone();
|
||||
|
||||
let local_in_flight = local_in_flight.clone();
|
||||
local_in_flight.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
IN_FLIGHT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
tokio::task::spawn_local(async move {
|
||||
f(instance).await;
|
||||
|
||||
IN_FLIGHT.fetch_sub(1, Ordering::Relaxed);
|
||||
local_in_flight.fetch_sub(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
// Yield before listening for more messages to give JS a chance to run.
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
Err(_) => {
|
||||
// Channel closed
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub struct SharedState {
|
||||
pub thread_id: u64,
|
||||
pub runtime: tokio::runtime::Runtime,
|
||||
pub conn: trailbase_sqlite::Connection,
|
||||
pub kv_store: KvStore,
|
||||
pub fs_root_path: Option<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
pub struct RuntimeInstance {
|
||||
engine: Engine,
|
||||
component: Component,
|
||||
linker: Linker<State>,
|
||||
|
||||
shared: Arc<SharedState>,
|
||||
}
|
||||
|
||||
impl RuntimeInstance {
|
||||
// pub fn new(
|
||||
// engine: Engine,
|
||||
// component: Component,
|
||||
// linker: Linker<State>,
|
||||
// shared_state: SharedState,
|
||||
// ) -> Result<Self, Error> {
|
||||
// // let mut linker = Linker::<State>::new(&engine);
|
||||
// //
|
||||
// // // Adds all the default WASI implementations: clocks, random, fs, ...
|
||||
// // add_to_linker_async(&mut linker)?;
|
||||
// //
|
||||
// // // Adds default HTTP interfaces - incoming and outgoing.
|
||||
// // wasmtime_wasi_http::add_only_http_to_linker_async(&mut linker)?;
|
||||
// //
|
||||
// // // Add default KV interfaces.
|
||||
// // trailbase_wasi_keyvalue::add_to_linker(&mut linker, |cx| {
|
||||
// // trailbase_wasi_keyvalue::WasiKeyValue::new(&cx.kv, &mut cx.resource_table)
|
||||
// // })?;
|
||||
// //
|
||||
// // // Host interfaces.
|
||||
// // trailbase::runtime::host_endpoint::add_to_linker::<_, HasSelf<State>>(&mut linker, |s|
|
||||
// s)?;
|
||||
//
|
||||
// return Ok(Self {
|
||||
// engine,
|
||||
// component,
|
||||
// linker,
|
||||
// shared: Arc::new(shared_state),
|
||||
// });
|
||||
// }
|
||||
|
||||
fn new_store(&self) -> Result<Store<State>, Error> {
|
||||
let mut wasi_ctx = WasiCtxBuilder::new();
|
||||
wasi_ctx.inherit_stdio();
|
||||
wasi_ctx.stdin(wasmtime_wasi::p2::pipe::ClosedInputStream);
|
||||
// wasi_ctx.stdout(wasmtime_wasi::p2::Stdout);
|
||||
// wasi_ctx.stderr(wasmtime_wasi::p2::Stderr);
|
||||
|
||||
wasi_ctx.args(&[""]);
|
||||
wasi_ctx.allow_tcp(false);
|
||||
wasi_ctx.allow_udp(false);
|
||||
wasi_ctx.allow_ip_name_lookup(true);
|
||||
|
||||
if let Some(ref path) = self.shared.fs_root_path {
|
||||
wasi_ctx
|
||||
.preopened_dir(path, "/", DirPerms::READ, FilePerms::READ)
|
||||
.map_err(|err| Error::Other(err.to_string()))?;
|
||||
}
|
||||
|
||||
return Ok(Store::new(
|
||||
&self.engine,
|
||||
State {
|
||||
resource_table: ResourceTable::new(),
|
||||
wasi_ctx: wasi_ctx.build(),
|
||||
http: WasiHttpCtx::new(),
|
||||
kv: WasiKeyValueCtx::new(self.shared.kv_store.clone()),
|
||||
shared: self.shared.clone(),
|
||||
tx: LockedTransaction(Rc::new(Mutex::new(None))),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn call_init(&self) -> Result<InitResult, Error> {
|
||||
let mut store = self.new_store()?;
|
||||
let bindings = Trailbase::instantiate_async(&mut store, &self.component, &self.linker).await?;
|
||||
|
||||
return Ok(
|
||||
bindings
|
||||
.trailbase_runtime_init_endpoint()
|
||||
.call_init(&mut store)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn call_incoming_http_handler(
|
||||
&self,
|
||||
request: hyper::Request<BoxBody<Bytes, hyper::Error>>,
|
||||
) -> Result<hyper::Response<wasmtime_wasi_http::body::HyperOutgoingBody>, Error> {
|
||||
let mut store = self.new_store()?;
|
||||
|
||||
let proxy = wasmtime_wasi_http::bindings::Proxy::instantiate_async(
|
||||
&mut store,
|
||||
&self.component,
|
||||
&self.linker,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let req = store.data_mut().new_incoming_request(
|
||||
wasmtime_wasi_http::bindings::http::types::Scheme::Http,
|
||||
request,
|
||||
)?;
|
||||
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel::<
|
||||
Result<hyper::Response<wasmtime_wasi_http::body::HyperOutgoingBody>, ErrorCode>,
|
||||
>();
|
||||
|
||||
let out = store.data_mut().new_response_outparam(sender)?;
|
||||
|
||||
// NOTE: wstd streams out responses in chunks of 2kB. Only once everything has been streamed,
|
||||
// `call_handle` will complete. This is also when the streaming response body completes.
|
||||
//
|
||||
// We cannot use `wasmtime_wasi::runtime::spawn` here, which aborts the call when the handle
|
||||
// gets dropped, since we're not awaiting the response stream here. We'd either have to consume
|
||||
// the entire response here, keep the handle alive or as we currently do use a non-aborting
|
||||
// spawn.
|
||||
//
|
||||
// In the current setup, if the listening side hangs-up the they call may not be aborted.
|
||||
// Depends on what the implementation does when the streaming body's receiving end gets
|
||||
// out of scope.
|
||||
let handle = self.shared.runtime.spawn(async move {
|
||||
proxy
|
||||
.wasi_http_incoming_handler()
|
||||
.call_handle(&mut store, req, out)
|
||||
.await
|
||||
});
|
||||
|
||||
return match receiver.await {
|
||||
Ok(Ok(resp)) => {
|
||||
// NOTE: We cannot await the completion `call_handle` here with `handle.await?;`, since
|
||||
// we're not consuming the response body, see above.
|
||||
Ok(resp)
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
handle
|
||||
.await
|
||||
.map_err(|err| Error::Other(err.to_string()))??;
|
||||
Err(Error::HttpErrorCode(err))
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("channel closed");
|
||||
handle
|
||||
.await
|
||||
.map_err(|err| Error::Other(err.to_string()))??;
|
||||
Err(Error::ChannelClosed)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn bytes_to_respone(
|
||||
bytes: Vec<u8>,
|
||||
) -> Result<wasmtime_wasi_http::types::HostFutureIncomingResponse, ErrorCode> {
|
||||
let resp = http::Response::builder()
|
||||
.status(200)
|
||||
.body(sqlite::bytes_to_body(Bytes::from_owner(bytes)))
|
||||
.map_err(|err| ErrorCode::InternalError(Some(err.to_string())))?;
|
||||
|
||||
return Ok(
|
||||
wasmtime_wasi_http::types::HostFutureIncomingResponse::ready(Ok(Ok(
|
||||
wasmtime_wasi_http::types::IncomingResponse {
|
||||
resp,
|
||||
worker: None,
|
||||
between_bytes_timeout: std::time::Duration::ZERO,
|
||||
},
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
fn to_sqlite_value(value: Value) -> trailbase_sqlite::Value {
|
||||
return match value {
|
||||
Value::Null => trailbase_sqlite::Value::Null,
|
||||
Value::Text(s) => trailbase_sqlite::Value::Text(s),
|
||||
Value::Real(f) => trailbase_sqlite::Value::Real(f),
|
||||
Value::Integer(i) => trailbase_sqlite::Value::Integer(i),
|
||||
Value::Blob(b) => trailbase_sqlite::Value::Blob(b),
|
||||
};
|
||||
}
|
||||
|
||||
fn from_sqlite_value(value: trailbase_sqlite::Value) -> Value {
|
||||
return match value {
|
||||
trailbase_sqlite::Value::Null => Value::Null,
|
||||
trailbase_sqlite::Value::Text(s) => Value::Text(s),
|
||||
trailbase_sqlite::Value::Real(f) => Value::Real(f),
|
||||
trailbase_sqlite::Value::Integer(i) => Value::Integer(i),
|
||||
trailbase_sqlite::Value::Blob(b) => Value::Blob(b),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use http::{Response, StatusCode};
|
||||
use http_body_util::combinators::BoxBody;
|
||||
use trailbase_wasm_common::{HttpContext, HttpContextKind};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_init() {
|
||||
let conn = trailbase_sqlite::Connection::open_in_memory().unwrap();
|
||||
let kv_store = KvStore::new();
|
||||
let runtime = Runtime::new(
|
||||
2,
|
||||
"../../client/testfixture/wasm/wasm_rust_guest_testfixture.wasm".into(),
|
||||
conn.clone(),
|
||||
kv_store,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
runtime
|
||||
.call(async |instance| {
|
||||
instance.call_init().await.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = send_http_request(
|
||||
&runtime,
|
||||
"http://localhost:4000/transaction",
|
||||
"/transaction",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
conn
|
||||
.query_row_f("SELECT COUNT(*) FROM tx;", (), |row| row.get::<_, i64>(0))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transaction() {
|
||||
let conn = trailbase_sqlite::Connection::open_in_memory().unwrap();
|
||||
let kv_store = KvStore::new();
|
||||
let runtime = Arc::new(
|
||||
Runtime::new(
|
||||
2,
|
||||
"../../client/testfixture/wasm/wasm_rust_guest_testfixture.wasm".into(),
|
||||
conn.clone(),
|
||||
kv_store,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let futures: Vec<_> = (0..256)
|
||||
.map(|_| {
|
||||
let runtime = runtime.clone();
|
||||
tokio::spawn(async move {
|
||||
send_http_request(
|
||||
&runtime,
|
||||
"http://localhost:4000/transaction",
|
||||
"/transaction",
|
||||
)
|
||||
.await
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
for future in futures {
|
||||
future.await.unwrap().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_http_request(
|
||||
runtime: &Runtime,
|
||||
uri: &str,
|
||||
registered_path: &str,
|
||||
) -> Result<Response<BoxBody<Bytes, ErrorCode>>, Error> {
|
||||
fn to_header_value(context: &HttpContext) -> hyper::http::HeaderValue {
|
||||
return hyper::http::HeaderValue::from_bytes(
|
||||
&serde_json::to_vec(&context).unwrap_or_default(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let uri = uri.to_string();
|
||||
let registered_path = registered_path.to_string();
|
||||
return runtime
|
||||
.call(async |instance| {
|
||||
let context = HttpContext {
|
||||
kind: HttpContextKind::Http,
|
||||
registered_path,
|
||||
path_params: vec![],
|
||||
user: None,
|
||||
};
|
||||
|
||||
let request = hyper::Request::builder()
|
||||
.uri(uri)
|
||||
.header("__context", to_header_value(&context))
|
||||
.body(sqlite::bytes_to_body(Bytes::from_static(b"")))
|
||||
.unwrap();
|
||||
|
||||
return instance.call_incoming_http_handler(request).await;
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
225
crates/wasm-runtime-host/src/sqlite.rs
Normal file
225
crates/wasm-runtime-host/src/sqlite.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use bytes::Bytes;
|
||||
use http_body_util::{BodyExt, combinators::BoxBody};
|
||||
use rusqlite::Transaction;
|
||||
use self_cell::{MutBorrow, self_cell};
|
||||
use tokio::time::Duration;
|
||||
use trailbase_schema::json::{JsonError, rich_json_to_value, value_to_rich_json};
|
||||
use trailbase_sqlite::connection::ArcLockGuard;
|
||||
use trailbase_wasm_common::{SqliteRequest, SqliteResponse};
|
||||
use wasmtime_wasi_http::bindings::http::types::ErrorCode;
|
||||
|
||||
self_cell!(
|
||||
pub(crate) struct OwnedTx {
|
||||
owner: MutBorrow<ArcLockGuard>,
|
||||
|
||||
#[covariant]
|
||||
dependent: Transaction,
|
||||
}
|
||||
);
|
||||
|
||||
pub(crate) async fn new_tx(conn: trailbase_sqlite::Connection) -> Result<OwnedTx, rusqlite::Error> {
|
||||
for _ in 0..200 {
|
||||
let Some(lock) = conn.try_write_arc_lock_for(Duration::from_micros(100)) else {
|
||||
tokio::time::sleep(Duration::from_micros(400)).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
return OwnedTx::try_new(MutBorrow::new(lock), |owner| {
|
||||
return owner.borrow_mut().transaction();
|
||||
});
|
||||
}
|
||||
|
||||
return Err(rusqlite::Error::ToSqlConversionFailure(
|
||||
"Failed to acquire lock".into(),
|
||||
));
|
||||
}
|
||||
|
||||
async fn handle_sqlite_request_impl(
|
||||
conn: trailbase_sqlite::Connection,
|
||||
request: hyper::Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
|
||||
) -> Result<SqliteResponse, String> {
|
||||
return match request.uri().path() {
|
||||
// "/tx_begin" => {
|
||||
// let new_tx = new_tx(conn).await.map_err(sqlite_err)?;
|
||||
//
|
||||
// CURRENT_TX.with(|tx: &Mutex<_>| {
|
||||
// *tx.lock() = Some(new_tx);
|
||||
// });
|
||||
//
|
||||
// Ok(SqliteResponse::TxBegin)
|
||||
// }
|
||||
// "/tx_commit" => {
|
||||
// let tx = CURRENT_TX.with(|tx: &Mutex<_>| {
|
||||
// return tx.lock().take();
|
||||
// });
|
||||
// if let Some(tx) = tx {
|
||||
// // NOTE: this is the same as `tx.commit()` just w/o consuming.
|
||||
// let lock = tx.borrow_dependent();
|
||||
// lock.execute_batch("COMMIT").map_err(sqlite_err)?;
|
||||
// }
|
||||
//
|
||||
// Ok(SqliteResponse::TxCommit)
|
||||
// }
|
||||
// "/tx_execute" => {
|
||||
// let sqlite_request = to_request(request).await?;
|
||||
//
|
||||
// let params = json_values_to_sqlite_params(sqlite_request.params).map_err(sqlite_err)?;
|
||||
//
|
||||
// let rows_affected = CURRENT_TX.with(move |tx: &Mutex<_>| -> Result<usize, String> {
|
||||
// let Some(ref tx) = *tx.lock() else {
|
||||
// return Err("No open transaction".to_string());
|
||||
// };
|
||||
// let lock = tx.borrow_dependent();
|
||||
//
|
||||
// let mut stmt = lock.prepare(&sqlite_request.query).map_err(sqlite_err)?;
|
||||
//
|
||||
// params.bind(&mut stmt).map_err(sqlite_err)?;
|
||||
//
|
||||
// return stmt.raw_execute().map_err(sqlite_err);
|
||||
// })?;
|
||||
//
|
||||
// Ok(SqliteResponse::Execute { rows_affected })
|
||||
// }
|
||||
// "/tx_query " => {
|
||||
// let sqlite_request = to_request(request).await?;
|
||||
//
|
||||
// let params = json_values_to_sqlite_params(sqlite_request.params).map_err(sqlite_err)?;
|
||||
//
|
||||
// let rows = CURRENT_TX.with(move |tx: &Mutex<_>| -> Result<Rows, String> {
|
||||
// let Some(ref tx) = *tx.lock() else {
|
||||
// return Err("No open transaction".to_string());
|
||||
// };
|
||||
// let lock = tx.borrow_dependent();
|
||||
//
|
||||
// let mut stmt = lock.prepare(&sqlite_request.query).map_err(sqlite_err)?;
|
||||
//
|
||||
// params.bind(&mut stmt).map_err(sqlite_err)?;
|
||||
//
|
||||
// return Rows::from_rows(stmt.raw_query()).map_err(sqlite_err);
|
||||
// })?;
|
||||
//
|
||||
// let json_rows = rows
|
||||
// .iter()
|
||||
// .map(|row| -> Result<Vec<serde_json::Value>, String> {
|
||||
// return row_to_rich_json_array(row).map_err(sqlite_err);
|
||||
// })
|
||||
// .collect::<Result<Vec<_>, _>>()?;
|
||||
//
|
||||
// Ok(SqliteResponse::Query { rows: json_rows })
|
||||
// }
|
||||
// "/tx_rollback " => {
|
||||
// let tx = CURRENT_TX.with(|tx: &Mutex<_>| {
|
||||
// return tx.lock().take();
|
||||
// });
|
||||
// if let Some(tx) = tx {
|
||||
// // NOTE: this is the same as `tx.rollback()` just w/o consuming.
|
||||
// let lock = tx.borrow_dependent();
|
||||
// lock.execute_batch("ROLLBACK").map_err(sqlite_err)?;
|
||||
// }
|
||||
//
|
||||
// Ok(SqliteResponse::TxRollback)
|
||||
// }
|
||||
"/execute" => {
|
||||
let sqlite_request = to_request(request).await?;
|
||||
|
||||
let rows_affected = conn
|
||||
.execute(
|
||||
sqlite_request.query,
|
||||
json_values_to_sqlite_params(sqlite_request.params).map_err(sqlite_err)?,
|
||||
)
|
||||
.await
|
||||
.map_err(sqlite_err)?;
|
||||
|
||||
Ok(SqliteResponse::Execute { rows_affected })
|
||||
}
|
||||
"/query" => {
|
||||
let sqlite_request = to_request(request).await?;
|
||||
|
||||
let rows = conn
|
||||
.write_query_rows(
|
||||
sqlite_request.query,
|
||||
json_values_to_sqlite_params(sqlite_request.params).map_err(sqlite_err)?,
|
||||
)
|
||||
.await
|
||||
.map_err(sqlite_err)?;
|
||||
|
||||
let json_rows = rows
|
||||
.iter()
|
||||
.map(|row| -> Result<Vec<serde_json::Value>, String> {
|
||||
return row_to_rich_json_array(row).map_err(sqlite_err);
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(SqliteResponse::Query { rows: json_rows })
|
||||
}
|
||||
_ => Err("Not found".to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_sqlite_request(
|
||||
conn: trailbase_sqlite::Connection,
|
||||
request: hyper::Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
|
||||
) -> Result<wasmtime_wasi_http::types::IncomingResponse, ErrorCode> {
|
||||
return match handle_sqlite_request_impl(conn, request).await {
|
||||
Ok(response) => to_response(response),
|
||||
Err(err) => to_response(SqliteResponse::Error(err)),
|
||||
};
|
||||
}
|
||||
|
||||
async fn to_request(
|
||||
request: hyper::Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
|
||||
) -> Result<SqliteRequest, String> {
|
||||
let (_parts, body) = request.into_parts();
|
||||
let bytes: Bytes = body.collect().await.map_err(sqlite_err)?.to_bytes();
|
||||
return serde_json::from_slice(&bytes).map_err(sqlite_err);
|
||||
}
|
||||
|
||||
fn to_response(
|
||||
response: SqliteResponse,
|
||||
) -> Result<wasmtime_wasi_http::types::IncomingResponse, ErrorCode> {
|
||||
let body =
|
||||
serde_json::to_vec(&response).map_err(|err| ErrorCode::InternalError(Some(err.to_string())))?;
|
||||
|
||||
let resp = http::Response::builder()
|
||||
.status(200)
|
||||
.body(bytes_to_body(Bytes::from_owner(body)))
|
||||
.map_err(|err| ErrorCode::InternalError(Some(err.to_string())))?;
|
||||
|
||||
return Ok(wasmtime_wasi_http::types::IncomingResponse {
|
||||
resp,
|
||||
worker: None,
|
||||
between_bytes_timeout: std::time::Duration::ZERO,
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn json_values_to_sqlite_params(
|
||||
values: Vec<serde_json::Value>,
|
||||
) -> Result<Vec<trailbase_sqlite::Value>, JsonError> {
|
||||
return values.into_iter().map(rich_json_to_value).collect();
|
||||
}
|
||||
|
||||
pub fn row_to_rich_json_array(
|
||||
row: &trailbase_sqlite::Row,
|
||||
) -> Result<Vec<serde_json::Value>, JsonError> {
|
||||
return (0..row.column_count())
|
||||
.map(|i| -> Result<serde_json::Value, JsonError> {
|
||||
let value = row.get_value(i).ok_or(JsonError::ValueNotFound)?;
|
||||
return value_to_rich_json(value);
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bytes_to_body<E>(bytes: Bytes) -> BoxBody<Bytes, E> {
|
||||
BoxBody::new(http_body_util::Full::new(bytes).map_err(|_| unreachable!()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sqlite_err<E: std::error::Error>(err: E) -> String {
|
||||
return err.to_string();
|
||||
}
|
||||
|
||||
// #[inline]
|
||||
// fn empty<E>() -> BoxBody<Bytes, E> {
|
||||
// BoxBody::new(http_body_util::Empty::new().map_err(|_| unreachable!()))
|
||||
// }
|
||||
1
crates/wasm-runtime-host/wit
Symbolic link
1
crates/wasm-runtime-host/wit
Symbolic link
@@ -0,0 +1 @@
|
||||
../wasm-runtime-guest/wit
|
||||
@@ -46,7 +46,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
dev: false,
|
||||
disable_auth_ui: false,
|
||||
cors_allowed_origins: vec![],
|
||||
js_runtime_threads: None,
|
||||
..Default::default()
|
||||
},
|
||||
|state: AppState| async move {
|
||||
|
||||
Reference in New Issue
Block a user