lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 84320876bfc0a78cfad6d591f4021423be0345ea
parent 40942ae9e0c6f8c7f3ccefd837d9bfaf918d8f29
Author: triesap <tyson@radroots.org>
Date:   Mon, 22 Jun 2026 01:20:01 +0000

wasm: remove sql bridge crates

Diffstat:
MCargo.lock | 33---------------------------------
MCargo.toml | 5-----
Mcrates/sql_core/Cargo.toml | 10----------
Dcrates/sql_core/src/executor_wasm.rs | 68--------------------------------------------------------------------
Dcrates/sql_core/src/export_lock.rs | 46----------------------------------------------
Mcrates/sql_core/src/lib.rs | 12------------
Dcrates/sql_wasm_bridge/Cargo.toml | 20--------------------
Dcrates/sql_wasm_bridge/README | 24------------------------
Dcrates/sql_wasm_bridge/src/lib.rs | 138-------------------------------------------------------------------------------
Dcrates/sql_wasm_core/Cargo.toml | 38--------------------------------------
Dcrates/sql_wasm_core/README | 21---------------------
Dcrates/sql_wasm_core/src/lib.rs | 124-------------------------------------------------------------------------------
Mpolicy/coverage/policy.toml | 12------------
Mpolicy/coverage/profiles.toml | 5-----
Mspec/manifest.toml | 2--
15 files changed, 0 insertions(+), 558 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -4460,32 +4460,10 @@ name = "radroots_sql_core" version = "0.1.0-alpha.2" dependencies = [ "chrono", - "radroots_sql_wasm_bridge", "rusqlite", "serde", - "serde-wasm-bindgen", "serde_json", "uuid", - "wasm-bindgen", -] - -[[package]] -name = "radroots_sql_wasm_bridge" -version = "0.1.0-alpha.2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "radroots_sql_wasm_core" -version = "0.1.0-alpha.2" -dependencies = [ - "radroots_sql_core", - "radroots_sql_wasm_bridge", - "serde", - "serde-wasm-bindgen", - "wasm-bindgen", ] [[package]] @@ -5156,17 +5134,6 @@ dependencies = [ ] [[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] name = "serde_arrays" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -29,8 +29,6 @@ members = [ "crates/simplex_smp_crypto", "crates/simplex_smp_proto", "crates/simplex_smp_transport", - "crates/sql_wasm_bridge", - "crates/sql_wasm_core", "crates/sql_core", "crates/test_fixtures", "crates/replica_db_schema", @@ -89,8 +87,6 @@ radroots_simplex_interop_tests = { path = "crates/simplex_interop_tests", versio radroots_simplex_smp_crypto = { path = "crates/simplex_smp_crypto", version = "0.1.0-alpha.2", default-features = false } radroots_simplex_smp_proto = { path = "crates/simplex_smp_proto", version = "0.1.0-alpha.2", default-features = false } radroots_simplex_smp_transport = { path = "crates/simplex_smp_transport", version = "0.1.0-alpha.2", default-features = false } -radroots_sql_wasm_bridge = { path = "crates/sql_wasm_bridge", version = "0.1.0-alpha.2" } -radroots_sql_wasm_core = { path = "crates/sql_wasm_core", version = "0.1.0-alpha.2", default-features = false } radroots_sql_core = { path = "crates/sql_core", version = "0.1.0-alpha.2", default-features = false } radroots_test_fixtures = { path = "crates/test_fixtures", version = "0.1.0-alpha.2" } radroots_replica_db_schema = { path = "crates/replica_db_schema", version = "0.1.0-alpha.2", default-features = false } @@ -137,7 +133,6 @@ serde = { version = "1", default-features = false, features = [ "alloc", ] } serde_json = { version = "1", default-features = false, features = ["alloc"] } -serde-wasm-bindgen = { version = "0.6" } sha2 = { version = "0.10", default-features = false } sqlx = { version = "0.8.6", default-features = false } sp1-build = { version = "6.2.3" } diff --git a/crates/sql_core/Cargo.toml b/crates/sql_core/Cargo.toml @@ -19,21 +19,11 @@ crate-type = ["rlib"] default = ["std"] std = ["dep:chrono", "dep:uuid", "serde/std", "serde_json/std"] web = ["std", "uuid/js"] -bridge = [ - "std", - "web", - "dep:radroots_sql_wasm_bridge", - "dep:serde-wasm-bindgen", - "dep:wasm-bindgen", -] native = ["std", "dep:rusqlite"] embedded = [] [dependencies] serde_json = { workspace = true } -radroots_sql_wasm_bridge = { workspace = true, optional = true } -wasm-bindgen = { workspace = true, optional = true } -serde-wasm-bindgen = { workspace = true, optional = true } rusqlite = { workspace = true, features = ["bundled"], optional = true } chrono = { workspace = true, optional = true } serde = { workspace = true } diff --git a/crates/sql_core/src/executor_wasm.rs b/crates/sql_core/src/executor_wasm.rs @@ -1,68 +0,0 @@ -use crate::export_lock::{EXPORT_LOCK_ERR, export_lock_blocked}; -use crate::{ExecOutcome, SqlExecutor, error::SqlError}; - -pub struct WasmSqlExecutor; - -impl WasmSqlExecutor { - pub fn new() -> Self { - Self - } -} - -impl Default for WasmSqlExecutor { - fn default() -> Self { - Self::new() - } -} - -impl SqlExecutor for WasmSqlExecutor { - fn exec(&self, sql: &str, params_json: &str) -> Result<ExecOutcome, SqlError> { - if export_lock_blocked() { - return Err(SqlError::InvalidArgument(EXPORT_LOCK_ERR.to_string())); - } - let js = radroots_sql_wasm_bridge::exec(sql, params_json); - let v: serde_json::Value = serde_wasm_bindgen::from_value(js) - .map_err(|e| SqlError::SerializationError(e.to_string()))?; - let changes = v.get("changes").and_then(|x| x.as_i64()).unwrap_or(0); - let last_insert_id = v - .get("last_insert_id") - .or_else(|| v.get("lastInsertRowid")) - .and_then(|x| x.as_i64()) - .unwrap_or(0); - Ok(ExecOutcome { - changes, - last_insert_id, - }) - } - - fn query_raw(&self, sql: &str, params_json: &str) -> Result<String, SqlError> { - let js = radroots_sql_wasm_bridge::query(sql, params_json); - let v: serde_json::Value = serde_wasm_bindgen::from_value(js) - .map_err(|e| SqlError::SerializationError(e.to_string()))?; - Ok(v.to_string()) - } - - fn begin(&self) -> Result<(), SqlError> { - if export_lock_blocked() { - return Err(SqlError::InvalidArgument(EXPORT_LOCK_ERR.to_string())); - } - radroots_sql_wasm_bridge::begin_tx(); - Ok(()) - } - - fn commit(&self) -> Result<(), SqlError> { - if export_lock_blocked() { - return Err(SqlError::InvalidArgument(EXPORT_LOCK_ERR.to_string())); - } - radroots_sql_wasm_bridge::commit_tx(); - Ok(()) - } - - fn rollback(&self) -> Result<(), SqlError> { - if export_lock_blocked() { - return Err(SqlError::InvalidArgument(EXPORT_LOCK_ERR.to_string())); - } - radroots_sql_wasm_bridge::rollback_tx(); - Ok(()) - } -} diff --git a/crates/sql_core/src/export_lock.rs b/crates/sql_core/src/export_lock.rs @@ -1,46 +0,0 @@ -#![cfg(target_arch = "wasm32")] -#![forbid(unsafe_code)] - -use crate::error::SqlError; -use std::cell::Cell; -use std::sync::atomic::{AtomicBool, Ordering}; - -pub(crate) const EXPORT_LOCK_ERR: &str = "replica db export in progress"; - -static EXPORT_LOCK_ACTIVE: AtomicBool = AtomicBool::new(false); - -thread_local! { - static EXPORT_LOCK_BYPASS: Cell<bool> = Cell::new(false); -} - -pub fn export_lock_begin() -> Result<(), SqlError> { - let was_active = EXPORT_LOCK_ACTIVE.swap(true, Ordering::SeqCst); - if was_active { - return Err(SqlError::InvalidArgument(EXPORT_LOCK_ERR.to_string())); - } - Ok(()) -} - -pub fn export_lock_end() { - EXPORT_LOCK_ACTIVE.store(false, Ordering::SeqCst); -} - -pub fn export_lock_active() -> bool { - EXPORT_LOCK_ACTIVE.load(Ordering::SeqCst) -} - -pub fn with_export_lock_bypass<T>(f: impl FnOnce() -> T) -> T { - EXPORT_LOCK_BYPASS.with(|flag| { - let prev = flag.replace(true); - let out = f(); - flag.set(prev); - out - }) -} - -pub(crate) fn export_lock_blocked() -> bool { - if !EXPORT_LOCK_ACTIVE.load(Ordering::SeqCst) { - return false; - } - EXPORT_LOCK_BYPASS.with(|flag| !flag.get()) -} diff --git a/crates/sql_core/src/lib.rs b/crates/sql_core/src/lib.rs @@ -5,18 +5,6 @@ extern crate alloc; pub mod error; pub mod migrations; -#[cfg(all(feature = "web", target_arch = "wasm32"))] -mod export_lock; -#[cfg(all(feature = "web", target_arch = "wasm32"))] -pub use export_lock::{ - export_lock_active, export_lock_begin, export_lock_end, with_export_lock_bypass, -}; - -#[cfg(all(feature = "bridge", target_arch = "wasm32"))] -mod executor_wasm; -#[cfg(all(feature = "bridge", target_arch = "wasm32"))] -pub use executor_wasm::WasmSqlExecutor; - #[cfg(all(feature = "native", feature = "std"))] mod executor_sqlite; #[cfg(all(feature = "native", feature = "std"))] diff --git a/crates/sql_wasm_bridge/Cargo.toml b/crates/sql_wasm_bridge/Cargo.toml @@ -1,20 +0,0 @@ -[package] -name = "radroots_sql_wasm_bridge" -publish = false -version = "0.1.0-alpha.2" -edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] -rust-version.workspace = true -license.workspace = true -description = "Host bridge ABI for WebAssembly SQL execution" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots_sql_wasm_bridge" -readme = "README" - -[features] -default = [] - -[dependencies] -js-sys = { workspace = true } -wasm-bindgen = { workspace = true } diff --git a/crates/sql_wasm_bridge/README b/crates/sql_wasm_bridge/README @@ -1,24 +0,0 @@ -# radroots_sql_wasm_bridge - -This is the README for `radroots_sql_wasm_bridge`, which provides a wasm SQL -bridge ABI for the `radroots` core libraries. - -## Overview - - * extern bridge hooks for SQL execution, query, and database export supplied - by the embedding host; - * transaction and savepoint helpers for begin, commit, and rollback flows; - * a wasm-first API surface with non-wasm shims used by tests; - * small bridge functions that return JavaScript values instead of defining a - database engine. - -## Copyright - -Except as otherwise noted, all files in the `radroots_sql_wasm_bridge` -distribution are - - Copyright (c) 2020-2026 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_sql_wasm_bridge` -distribution. diff --git a/crates/sql_wasm_bridge/src/lib.rs b/crates/sql_wasm_bridge/src/lib.rs @@ -1,138 +0,0 @@ -use wasm_bindgen::JsValue; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_name = __radroots_sql_wasm_exec)] - fn js_exec(sql: &str, params_json: &str) -> JsValue; - - #[wasm_bindgen(js_name = __radroots_sql_wasm_query)] - fn js_query(sql: &str, params_json: &str) -> JsValue; - - #[wasm_bindgen(js_name = __radroots_sql_wasm_export_bytes)] - fn js_export_bytes() -> JsValue; -} - -#[cfg(not(target_arch = "wasm32"))] -use std::sync::{Mutex, OnceLock}; - -#[cfg(not(target_arch = "wasm32"))] -type RecordedCall = (String, String); - -#[cfg(not(target_arch = "wasm32"))] -fn exec_calls() -> &'static Mutex<Vec<RecordedCall>> { - static EXEC_CALLS: OnceLock<Mutex<Vec<RecordedCall>>> = OnceLock::new(); - EXEC_CALLS.get_or_init(|| Mutex::new(Vec::new())) -} - -#[cfg(not(target_arch = "wasm32"))] -fn query_calls() -> &'static Mutex<Vec<RecordedCall>> { - static QUERY_CALLS: OnceLock<Mutex<Vec<RecordedCall>>> = OnceLock::new(); - QUERY_CALLS.get_or_init(|| Mutex::new(Vec::new())) -} - -#[cfg(not(target_arch = "wasm32"))] -fn export_calls() -> &'static Mutex<u64> { - static EXPORT_CALLS: OnceLock<Mutex<u64>> = OnceLock::new(); - EXPORT_CALLS.get_or_init(|| Mutex::new(0)) -} - -#[cfg(not(target_arch = "wasm32"))] -fn js_exec(sql: &str, params_json: &str) -> JsValue { - let mut calls = exec_calls().lock().expect("exec calls lock"); - calls.push((sql.to_string(), params_json.to_string())); - JsValue::NULL -} - -#[cfg(not(target_arch = "wasm32"))] -fn js_query(sql: &str, params_json: &str) -> JsValue { - let mut calls = query_calls().lock().expect("query calls lock"); - calls.push((sql.to_string(), params_json.to_string())); - JsValue::NULL -} - -#[cfg(not(target_arch = "wasm32"))] -fn js_export_bytes() -> JsValue { - let mut calls = export_calls().lock().expect("export calls lock"); - *calls += 1; - JsValue::NULL -} - -const SAVEPOINT: &str = "radroots_schema_tx"; - -pub fn exec(sql: &str, params_json: &str) -> JsValue { - js_exec(sql, params_json) -} - -pub fn query(sql: &str, params_json: &str) -> JsValue { - js_query(sql, params_json) -} - -pub fn export_bytes() -> JsValue { - js_export_bytes() -} - -pub fn begin_tx() { - let _ = js_exec(&format!("savepoint {}", SAVEPOINT), "[]"); -} - -pub fn commit_tx() { - let _ = js_exec(&format!("release savepoint {}", SAVEPOINT), "[]"); -} - -pub fn rollback_tx() { - let _ = js_exec(&format!("rollback to savepoint {}", SAVEPOINT), "[]"); - let _ = js_exec(&format!("release savepoint {}", SAVEPOINT), "[]"); -} - -#[cfg(test)] -mod tests { - use super::{ - begin_tx, commit_tx, exec, exec_calls, export_bytes, export_calls, query, query_calls, - rollback_tx, - }; - - #[test] - fn exec_query_export_delegate_to_js_hooks() { - let _ = exec("select 1", "[]"); - let _ = query("select 2", "[1]"); - let _ = export_bytes(); - - let exec_len = exec_calls().lock().map(|calls| calls.len()).unwrap_or(0); - let query_len = query_calls().lock().map(|calls| calls.len()).unwrap_or(0); - let export_len = export_calls().lock().map(|calls| *calls).unwrap_or(0); - assert!(exec_len >= 1); - assert!(query_len >= 1); - assert!(export_len >= 1); - } - - #[test] - fn tx_helpers_emit_expected_savepoint_statements() { - begin_tx(); - commit_tx(); - rollback_tx(); - - let calls = exec_calls() - .lock() - .map(|calls| calls.clone()) - .unwrap_or_default(); - assert!( - calls - .iter() - .any(|(sql, _)| sql == "savepoint radroots_schema_tx") - ); - assert!( - calls - .iter() - .any(|(sql, _)| sql == "release savepoint radroots_schema_tx") - ); - assert!( - calls - .iter() - .any(|(sql, _)| sql == "rollback to savepoint radroots_schema_tx") - ); - } -} diff --git a/crates/sql_wasm_core/Cargo.toml b/crates/sql_wasm_core/Cargo.toml @@ -1,38 +0,0 @@ -[package] -name = "radroots_sql_wasm_core" -publish = false -version = "0.1.0-alpha.2" -edition.workspace = true -authors = ["Tyson Lupul <tyson@radroots.org>"] -rust-version.workspace = true -license.workspace = true -description = "WebAssembly SQL runtime facade and execution entry points" -repository.workspace = true -homepage.workspace = true -documentation = "https://docs.rs/radroots_sql_wasm_core" -readme = "README" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = ["bridge"] -bridge = [ - "radroots_sql_core/bridge", - "dep:radroots_sql_wasm_bridge", - "dep:serde", - "dep:serde-wasm-bindgen", - "dep:wasm-bindgen", -] - -[dependencies] -radroots_sql_core = { workspace = true } -radroots_sql_wasm_bridge = { workspace = true, optional = true } -serde = { workspace = true, optional = true } -serde-wasm-bindgen = { workspace = true, optional = true } -wasm-bindgen = { workspace = true, optional = true } - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(coverage_nightly)', -] } diff --git a/crates/sql_wasm_core/README b/crates/sql_wasm_core/README @@ -1,21 +0,0 @@ -# radroots_sql_wasm_core - -This is the README for `radroots_sql_wasm_core`, which provides a wasm SQL -runtime facade for the `radroots` core libraries. - -## Overview - - * shared wasm entry points for SQL execution, query, export, and transaction - control; - * host bridge integration for browser and worker SQL runtimes; - * small JSON parsing and JavaScript error helpers for wasm callers. - -## Copyright - -Except as otherwise noted, all files in the `radroots_sql_wasm_core` -distribution are - - Copyright (c) 2020-2026 Tyson Lupul - -For information on usage and redistribution, and for a DISCLAIMER OF ALL -WARRANTIES, see LICENSE included in the `radroots_sql_wasm_core` distribution. diff --git a/crates/sql_wasm_core/src/lib.rs b/crates/sql_wasm_core/src/lib.rs @@ -1,124 +0,0 @@ -#![cfg_attr(coverage_nightly, feature(coverage_attribute))] - -#[cfg(feature = "bridge")] -use radroots_sql_core::error::SqlError; - -#[cfg(feature = "bridge")] -use radroots_sql_core::utils; - -#[cfg(feature = "bridge")] -use serde::de::DeserializeOwned; - -#[cfg(feature = "bridge")] -use wasm_bindgen::JsValue; -#[cfg(all(feature = "bridge", target_arch = "wasm32"))] -use wasm_bindgen::prelude::*; - -#[cfg(feature = "bridge")] -pub fn parse_json<T: DeserializeOwned>(s: &str) -> Result<T, SqlError> { - utils::parse_json(s) -} - -#[cfg(feature = "bridge")] -pub fn err_js(err: SqlError) -> JsValue { - err_js_value(err) -} - -#[cfg(all(feature = "bridge", target_arch = "wasm32"))] -fn err_js_value(err: SqlError) -> JsValue { - match err_js_with_encoder(err, |err| { - let value = err.to_json(); - serde_wasm_bindgen::to_value(&value).map_err(|_| ()) - }) { - Ok(value) => value, - Err(err) => JsValue::from_str(&err.to_string()), - } -} - -#[cfg(all(feature = "bridge", not(target_arch = "wasm32")))] -fn err_js_value(err: SqlError) -> JsValue { - let _ = err.to_json(); - JsValue::NULL -} - -#[cfg(all(feature = "bridge", target_arch = "wasm32"))] -fn err_js_with_encoder( - err: SqlError, - encode: impl FnOnce(&SqlError) -> Result<JsValue, ()>, -) -> Result<JsValue, SqlError> { - encode(&err).map_err(|()| err) -} - -#[cfg(feature = "bridge")] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = exec_sql))] -pub fn exec_sql(sql: &str, params_json: &str) -> JsValue { - radroots_sql_wasm_bridge::exec(sql, params_json) -} - -#[cfg(feature = "bridge")] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = query_sql))] -pub fn query_sql(sql: &str, params_json: &str) -> JsValue { - radroots_sql_wasm_bridge::query(sql, params_json) -} - -#[cfg(feature = "bridge")] -pub fn export_bytes() -> JsValue { - radroots_sql_wasm_bridge::export_bytes() -} - -#[cfg(feature = "bridge")] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = begin_tx))] -pub fn begin_tx() { - radroots_sql_wasm_bridge::begin_tx() -} - -#[cfg(feature = "bridge")] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = commit_tx))] -pub fn commit_tx() { - radroots_sql_wasm_bridge::commit_tx() -} - -#[cfg(feature = "bridge")] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = rollback_tx))] -pub fn rollback_tx() { - radroots_sql_wasm_bridge::rollback_tx() -} - -#[cfg(all(test, feature = "bridge"))] -#[cfg_attr(coverage_nightly, coverage(off))] -mod tests { - use std::collections::BTreeMap; - - use radroots_sql_core::error::SqlError; - - use super::{ - begin_tx, commit_tx, err_js, exec_sql, export_bytes, parse_json, query_sql, rollback_tx, - }; - - #[test] - fn parse_json_reports_valid_and_invalid_payloads() { - let parsed: BTreeMap<String, u64> = parse_json(r#"{"count":2}"#).expect("parse json"); - assert_eq!(parsed.get("count"), Some(&2)); - assert!(matches!( - parse_json::<BTreeMap<String, u64>>("{"), - Err(SqlError::SerializationError(_)) - )); - } - - #[test] - fn err_js_accepts_sql_errors() { - let _ = err_js(SqlError::Internal); - let _ = err_js(SqlError::UnsupportedPlatform); - } - - #[test] - fn sql_entrypoints_delegate_to_bridge() { - let _ = exec_sql("select 1", "[]"); - let _ = query_sql("select 2", "[2]"); - let _ = export_bytes(); - - begin_tx(); - commit_tx(); - rollback_tx(); - } -} diff --git a/policy/coverage/policy.toml b/policy/coverage/policy.toml @@ -25,16 +25,6 @@ require_branches = false temporary = true reason = "branch coverage is not applicable while the crate has no measured branch records" -[overrides.radroots_sql_wasm_bridge] -require_branches = false -temporary = true -reason = "branch coverage is not applicable while the crate has no measured branch records" - -[overrides.radroots_sql_wasm_core] -require_branches = false -temporary = true -reason = "branch coverage is not applicable while the crate has no measured branch records" - [overrides.radroots_test_fixtures] require_branches = false temporary = true @@ -73,8 +63,6 @@ crates = [ "radroots_sp1_guest_trade", "radroots_sp1_host_trade", "radroots_sql_core", - "radroots_sql_wasm_bridge", - "radroots_sql_wasm_core", "radroots_test_fixtures", "radroots_trade", "radroots_types", diff --git a/policy/coverage/profiles.toml b/policy/coverage/profiles.toml @@ -17,8 +17,3 @@ test_threads = 1 no_default_features = true features = ["std"] test_threads = 1 - -[profiles.crates."radroots_sql_wasm_core"] -no_default_features = true -features = ["bridge"] -test_threads = 1 diff --git a/spec/manifest.toml b/spec/manifest.toml @@ -49,8 +49,6 @@ deferred_publication = [ "radroots_net", "radroots_nostr_runtime", "radroots_nostr_ndb", - "radroots_sql_wasm_bridge", - "radroots_sql_wasm_core", "radroots_simplex_chat_proto", "radroots_simplex_smp_proto", "radroots_sp1_guest_trade",