diff --git a/Cargo.lock b/Cargo.lock index 793259210..e5d5642f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,6 +331,11 @@ name = "cc" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] [[package]] name = "cfg-if" @@ -414,6 +419,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -528,7 +542,7 @@ dependencies = [ "itertools", "log", "smallvec", - "wasmparser", + "wasmparser 0.202.0", "wasmtime-types", ] @@ -541,6 +555,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crypto-common" version = "0.1.6" @@ -615,6 +654,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -674,6 +734,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "fd-lock" version = "4.0.2" @@ -887,6 +953,9 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "heck" @@ -894,6 +963,13 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hello-component-client" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "hello-nats-client" version = "0.1.0" @@ -1009,14 +1085,14 @@ dependencies = [ "clap", "futures", "http 1.1.0", - "hyper-rustls", + "hyper-rustls 0.27.1", "hyper-util", "rustls 0.23.5", "rustls-native-certs 0.7.0", "tokio", "tracing", "tracing-subscriber", - "webpki-roots", + "webpki-roots 0.26.1", "wrpc-interface-http", "wrpc-transport", "wrpc-transport-nats", @@ -1079,6 +1155,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.28", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.1" @@ -1188,6 +1278,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_executable" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +dependencies = [ + "winapi", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -1209,6 +1308,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -1236,6 +1344,16 @@ version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1322,6 +1440,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nix-nar" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e412c3138d9fcb384da86b4a068c11bad373441eb5c4b89406de4faf649fa02" +dependencies = [ + "is_executable", + "thiserror", +] + [[package]] name = "nkeys" version = "0.3.2" @@ -1475,6 +1603,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "platforms" version = "3.4.0" @@ -1550,6 +1684,37 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regalloc2" version = "0.9.3" @@ -1622,6 +1787,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -1629,17 +1795,23 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", + "tokio-rustls 0.24.1", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", + "webpki-roots 0.25.4", "winreg", ] @@ -1910,6 +2082,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2004,6 +2185,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spdx" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" +dependencies = [ + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" @@ -2104,13 +2294,25 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "test-helpers" version = "0.0.0" dependencies = [ "codegen-macro", "wit-bindgen-core", - "wit-parser", + "wit-parser 0.207.0", ] [[package]] @@ -2285,6 +2487,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -2408,6 +2644,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -2464,6 +2712,14 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi-keyvalue-component-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -2539,6 +2795,80 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d996306fb3aeaee0d9157adbe2f670df0236caf19f6728b221e92d0f27b3fe17" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-metadata" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094aea3cb90e09f16ee25a4c0e324b3e8c934e7fd838bfa039aef5352f44a917" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder 0.202.0", + "wasmparser 0.202.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2c44e62d325ce9253f88c01f0f67be121356767d12f2f13e701fdcd99e1f5b0" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder 0.207.0", + "wasmparser 0.207.0", +] + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmcloud-component-adapters" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2f03df5ce84548110521426695ac9d4cc5ab591953d9a6dd2165ce9968055a" +dependencies = [ + "anyhow", + "base64 0.21.7", + "futures", + "nix-nar", + "once_cell", + "reqwest", + "serde", + "serde_json", + "sha2", + "tempfile", + "tokio", + "tokio-util", +] + [[package]] name = "wasmparser" version = "0.202.0" @@ -2550,6 +2880,19 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" +dependencies = [ + "ahash", + "bitflags 2.5.0", + "hashbrown 0.14.5", + "indexmap", + "semver", +] + [[package]] name = "wasmprinter" version = "0.202.0" @@ -2557,7 +2900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab1cc9508685eef9502e787f4d4123745f5651a1e29aec047645d3cac1e2da7a" dependencies = [ "anyhow", - "wasmparser", + "wasmparser 0.202.0", ] [[package]] @@ -2566,6 +2909,7 @@ version = "20.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4af5cb32045daee8476711eb12b8b71275c2dd1fc7a58cc2a11b33ce9205f6a2" dependencies = [ + "addr2line", "anyhow", "async-trait", "bincode", @@ -2579,13 +2923,16 @@ dependencies = [ "object 0.33.0", "once_cell", "paste", + "rayon", "rustix", "semver", "serde", "serde_derive", "serde_json", "target-lexicon", - "wasmparser", + "wasm-encoder 0.202.0", + "wasmparser 0.202.0", + "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", "wasmtime-cranelift", @@ -2595,6 +2942,7 @@ dependencies = [ "wasmtime-runtime", "wasmtime-slab", "wasmtime-winch", + "wat", "windows-sys 0.52.0", ] @@ -2607,6 +2955,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "wasmtime-cache" +version = "20.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3aa2de7189ea6b3270727d0027790494aec5e7101ca50da3f9549a86628cae4" +dependencies = [ + "anyhow", + "base64 0.21.7", + "bincode", + "directories-next", + "log", + "rustix", + "serde", + "serde_derive", + "sha2", + "toml", + "windows-sys 0.52.0", + "zstd", +] + [[package]] name = "wasmtime-component-macro" version = "20.0.2" @@ -2619,7 +2987,7 @@ dependencies = [ "syn", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser", + "wit-parser 0.202.0", ] [[package]] @@ -2647,7 +3015,7 @@ dependencies = [ "object 0.33.0", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.202.0", "wasmtime-environ", "wasmtime-versioned-export-macros", ] @@ -2660,17 +3028,19 @@ checksum = "ad72e2e3f7ea5b50fedf66dd36ba24634e4f445c370644683b433d45d88f6126" dependencies = [ "anyhow", "bincode", + "cpp_demangle", "cranelift-entity", "gimli", "indexmap", "log", "object 0.33.0", + "rustc-demangle", "serde", "serde_derive", "target-lexicon", "thiserror", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.202.0", + "wasmparser 0.202.0", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -2722,6 +3092,7 @@ dependencies = [ "psm", "rustix", "sptr", + "wasm-encoder 0.202.0", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-fiber", @@ -2746,7 +3117,7 @@ dependencies = [ "serde", "serde_derive", "thiserror", - "wasmparser", + "wasmparser 0.202.0", ] [[package]] @@ -2810,7 +3181,7 @@ dependencies = [ "tracing", "wasmtime", "wasmtime-wasi", - "webpki-roots", + "webpki-roots 0.26.1", ] [[package]] @@ -2824,7 +3195,7 @@ dependencies = [ "gimli", "object 0.33.0", "target-lexicon", - "wasmparser", + "wasmparser 0.202.0", "wasmtime-cranelift", "wasmtime-environ", "winch-codegen", @@ -2839,7 +3210,29 @@ dependencies = [ "anyhow", "heck 0.4.1", "indexmap", - "wit-parser", + "wit-parser 0.202.0", +] + +[[package]] +name = "wast" +version = "207.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e40be9fd494bfa501309487d2dc0b3f229be6842464ecbdc54eac2679c84c93" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.207.0", +] + +[[package]] +name = "wat" +version = "1.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb2b15e2d5f300f5e1209e7dc237f2549edbd4203655b6c6cab5cf180561ee7" +dependencies = [ + "wast", ] [[package]] @@ -2852,6 +3245,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webpki-roots" version = "0.26.1" @@ -2895,7 +3294,7 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.202.0", "wasmtime-cranelift", "wasmtime-environ", ] @@ -3048,6 +3447,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -3068,6 +3476,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "wit-bindgen" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb4e7653763780be47e38f479e9aa83c768aa6a3b2ed086dc2826fdbbb7e7f5" +dependencies = [ + "wit-bindgen-rt", + "wit-bindgen-rust-macro", +] + [[package]] name = "wit-bindgen-core" version = "0.24.0" @@ -3075,7 +3493,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b67e11c950041849a10828c7600ea62a4077c01e8af72e8593253575428f91b" dependencies = [ "anyhow", - "wit-parser", + "wit-parser 0.202.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0780cf7046630ed70f689a098cd8d56c5c3b22f2a7379bbdb088879963ff96" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30acbe8fb708c3a830a33c4cb705df82659bf831b492ec6ca1a17a369cfeeafb" +dependencies = [ + "anyhow", + "heck 0.4.1", + "indexmap", + "wasm-metadata 0.202.0", + "wit-bindgen-core", + "wit-component 0.202.0", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b1b06eae85feaecdf9f2854f7cac124e00d5a6e5014bfb02eb1ecdeb5f265b9" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", ] [[package]] @@ -3131,6 +3586,44 @@ dependencies = [ "wit-bindgen-wrpc-rust", ] +[[package]] +name = "wit-component" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c836b1fd9932de0431c1758d8be08212071b6bba0151f7bac826dbc4312a2a9" +dependencies = [ + "anyhow", + "bitflags 2.5.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.202.0", + "wasm-metadata 0.202.0", + "wasmparser 0.202.0", + "wit-parser 0.202.0", +] + +[[package]] +name = "wit-component" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a411ff9c471737091b2c1a738a25031029fc4d0b8f1a60bef0e68906e9f6534b" +dependencies = [ + "anyhow", + "bitflags 2.5.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.207.0", + "wasm-metadata 0.207.0", + "wasmparser 0.207.0", + "wit-parser 0.207.0", +] + [[package]] name = "wit-parser" version = "0.202.0" @@ -3146,7 +3639,25 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.202.0", +] + +[[package]] +name = "wit-parser" +version = "0.207.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c83dab33a9618d86cfe3563cc864deffd08c17efc5db31a3b7cd1edeffe6e1" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.207.0", ] [[package]] @@ -3167,18 +3678,30 @@ dependencies = [ "serde_json", "tokio", "tracing", - "tracing-subscriber", "url", "wit-bindgen-core", "wit-bindgen-wrpc", "wit-bindgen-wrpc-go", "wit-bindgen-wrpc-rust", + "wrpc-cli", "wrpc-interface-blobstore", "wrpc-interface-http", "wrpc-runtime-wasmtime", "wrpc-transport", "wrpc-transport-nats", "wrpc-types", + "wrpc-wasmtime-nats-cli", +] + +[[package]] +name = "wrpc-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-nats", + "tokio", + "tracing-subscriber", + "url", ] [[package]] @@ -3266,7 +3789,38 @@ dependencies = [ "serde", "serde_json", "tracing", - "wit-parser", + "wit-parser 0.207.0", +] + +[[package]] +name = "wrpc-wasmtime-nats-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-nats", + "clap", + "futures", + "hyper 1.3.1", + "hyper-util", + "reqwest", + "tokio", + "tracing", + "tracing-subscriber", + "url", + "wasmcloud-component-adapters", + "wasmparser 0.207.0", + "wasmtime", + "wasmtime-wasi", + "wasmtime-wasi-http", + "wit-bindgen-wrpc", + "wit-component 0.207.0", + "wit-parser 0.207.0", + "wrpc-cli", + "wrpc-interface-http", + "wrpc-runtime-wasmtime", + "wrpc-transport", + "wrpc-transport-nats", + "wrpc-types", ] [[package]] @@ -3294,3 +3848,31 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 69d2e95f0..b634c2e85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,14 +20,37 @@ repository = "https://github.com/wrpc/wrpc" members = ["crates/*", "examples/rust/*"] [features] -default = ["nats", "wasmtime"] +default = ["bin", "nats", "wasmtime"] -nats = ["dep:async-nats", "dep:wrpc-transport-nats"] +bin = [ + "dep:clap", + "dep:serde", + "dep:serde_json", + "dep:tokio", + "dep:url", + "dep:wit-bindgen-core", + "dep:wit-bindgen-wrpc-go", + "dep:wrpc-cli", + "dep:wrpc-wasmtime-nats-cli", + "tokio/rt-multi-thread", + "tokio/sync", + "wit-bindgen-wrpc-go/clap", + "wit-bindgen-wrpc-rust/clap", +] +nats = ["dep:async-nats", "dep:wrpc-transport-nats", "wrpc-cli/nats"] wasmtime = ["dep:wrpc-runtime-wasmtime"] +[[bin]] +name = "wit-bindgen-wrpc" +required-features = ["bin"] + [[bin]] name = "wrpc-keyvalue-nats" -required-features = ["nats"] +required-features = ["bin", "nats"] + +[[bin]] +name = "wrpc-wasmtime-nats" +required-features = ["bin", "nats", "wasmtime"] [dependencies] anyhow = { workspace = true, features = ["std"] } @@ -40,29 +63,24 @@ clap = { workspace = true, features = [ "std", "suggestions", "usage", -] } -serde = { workspace = true } -serde_json = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "sync"] } +], optional = true } +serde = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } tracing = { workspace = true, features = ["attributes"] } -tracing-subscriber = { workspace = true, features = [ - "ansi", - "env-filter", - "fmt", - "smallvec", - "tracing-log", -] } -url = { workspace = true } -wit-bindgen-core = { workspace = true } +url = { workspace = true, optional = true } +wit-bindgen-core = { workspace = true, optional = true } wit-bindgen-wrpc = { workspace = true } -wit-bindgen-wrpc-go = { workspace = true, features = ["clap"] } -wit-bindgen-wrpc-rust = { workspace = true, features = ["clap"] } +wit-bindgen-wrpc-go = { workspace = true, optional = true } +wit-bindgen-wrpc-rust = { workspace = true, optional = true } +wrpc-cli = { workspace = true, optional = true } wrpc-interface-blobstore = { workspace = true } wrpc-interface-http = { workspace = true } wrpc-runtime-wasmtime = { workspace = true, optional = true } wrpc-transport = { workspace = true } wrpc-transport-nats = { workspace = true, optional = true } wrpc-types = { workspace = true } +wrpc-wasmtime-nats-cli = { workspace = true, optional = true } [dev-dependencies] anyhow = { workspace = true } @@ -75,6 +93,7 @@ hyper = { workspace = true, features = ["server"] } hyper-util = { workspace = true, features = ["server-auto", "tokio"] } reqwest = { workspace = true } tokio = { workspace = true, features = ["process"] } +wrpc-cli = { workspace = true } wrpc-interface-http = { workspace = true, features = [ "http", "http-body", @@ -113,19 +132,25 @@ tower = { version = "0.4", default-features = false } tracing = { version = "0.1", default-features = false } tracing-subscriber = { version = "0.3", default-features = false } url = { version = "2", default-features = false } +wasmcloud-component-adapters = { version = "0.9", default-features = false } +wasmparser = { version = "0.207", default-features = false } wasmtime = { version = "20", default-features = false } wasmtime-wasi = { version = "20", default-features = false } wasmtime-wasi-http = { version = "20", default-features = false } webpki-roots = { version = "0.26", default-features = false } +wit-bindgen = { version = "0.24", default-features = false } wit-bindgen-core = { version = "0.24", default-features = false } wit-bindgen-wrpc = { version = "0.3.6", default-features = false, path = "./crates/wit-bindgen" } wit-bindgen-wrpc-go = { version = "0.1.0", default-features = false, path = "./crates/wit-bindgen-go" } wit-bindgen-wrpc-rust = { version = "0.3.3", default-features = false, path = "./crates/wit-bindgen-rust" } wit-bindgen-wrpc-rust-macro = { version = "0.3.4", default-features = false, path = "./crates/wit-bindgen-rust-macro" } -wit-parser = { version = "0.202", default-features = false } +wit-component = { version = "0.207", default-features = false } +wit-parser = { version = "0.207", default-features = false } +wrpc-cli = { version = "*", path = "./crates/cli", default-features = false } wrpc-interface-blobstore = { version = "0.16", path = "./crates/interface-blobstore", default-features = false } wrpc-interface-http = { version = "0.22", path = "./crates/interface-http", default-features = false } wrpc-runtime-wasmtime = { version = "0.16", path = "./crates/runtime-wasmtime", default-features = false } wrpc-transport = { version = "0.24.2", path = "./crates/transport", default-features = false } wrpc-transport-nats = { version = "0.21", path = "./crates/transport-nats", default-features = false } wrpc-types = { version = "0.6", path = "./crates/types", default-features = false } +wrpc-wasmtime-nats-cli = { version = "*", path = "./crates/wasmtime-nats-cli", default-features = false } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 000000000..769d3c75f --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "wrpc-cli" +version = "0.1.0" +description = "wRPC CLI" +publish = false + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[features] +default = ["nats"] +nats = ["dep:async-nats", "dep:tokio", "tokio/sync", "dep:url"] + +[dependencies] +anyhow = { workspace = true } +async-nats = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } +tracing-subscriber = { workspace = true, features = [ + "ansi", + "env-filter", + "fmt", + "smallvec", + "tracing-log", +] } +url = { workspace = true, optional = true } diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs new file mode 100644 index 000000000..f6e19896d --- /dev/null +++ b/crates/cli/src/lib.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "nats")] +pub mod nats; +pub mod tracing; diff --git a/crates/cli/src/nats.rs b/crates/cli/src/nats.rs new file mode 100644 index 000000000..573f2ff1d --- /dev/null +++ b/crates/cli/src/nats.rs @@ -0,0 +1,34 @@ +use anyhow::Context as _; +use tokio::sync::mpsc; +use url::Url; + +pub const DEFAULT_URL: &str = "nats://127.0.0.1:4222"; + +/// Connect to NATS.io server and ensure that the connection is fully established before +/// returning the resulting [`async_nats::Client`] +pub async fn connect(url: Url) -> anyhow::Result { + let (conn_tx, mut conn_rx) = mpsc::channel(1); + let client = async_nats::connect_with_options( + String::from(url), + async_nats::ConnectOptions::new() + .retry_on_initial_connect() + .event_callback(move |event| { + let conn_tx = conn_tx.clone(); + async move { + if let async_nats::Event::Connected = event { + conn_tx + .send(()) + .await + .expect("failed to send NATS.io server connection notification"); + } + } + }), + ) + .await + .context("failed to connect to NATS.io server")?; + conn_rx + .recv() + .await + .context("failed to await NATS.io server connection to be established")?; + Ok(client) +} diff --git a/crates/cli/src/tracing.rs b/crates/cli/src/tracing.rs new file mode 100644 index 000000000..fa68c16c9 --- /dev/null +++ b/crates/cli/src/tracing.rs @@ -0,0 +1,14 @@ +use tracing_subscriber::layer::SubscriberExt as _; +use tracing_subscriber::util::SubscriberInitExt as _; + +pub fn env_filter() -> tracing_subscriber::EnvFilter { + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")) +} + +pub fn init() { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer().compact().without_time()) + .with(env_filter()) + .init(); +} diff --git a/crates/wasmtime-nats-cli/Cargo.toml b/crates/wasmtime-nats-cli/Cargo.toml new file mode 100644 index 000000000..3555faf80 --- /dev/null +++ b/crates/wasmtime-nats-cli/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "wrpc-wasmtime-nats-cli" +version = "0.1.0" +description = "wRPC Wasmtime NATS CLI" +publish = false + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = { workspace = true } +async-nats = { workspace = true } +clap = { workspace = true, features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] } +futures = { workspace = true } +hyper = { workspace = true, features = ["server"] } +hyper-util = { workspace = true, features = ["server-auto", "tokio"] } +reqwest = { workspace = true } +tokio = { workspace = true, features = ["fs"] } +tracing = { workspace = true, features = ["attributes"] } +tracing-subscriber = { workspace = true, features = [ + "ansi", + "env-filter", + "fmt", +] } +url = { workspace = true } +wasmcloud-component-adapters = { workspace = true } +wasmparser = { workspace = true } +wasmtime = { workspace = true, features = [ + "addr2line", + "async", + "cache", + "coredump", + "cranelift", + "demangle", + "gc", + "parallel-compilation", + "runtime", + "threads", + "wat", +] } +wasmtime-wasi = { workspace = true } +wasmtime-wasi-http = { workspace = true } +wit-bindgen-wrpc = { workspace = true } +wit-component = { workspace = true } +wit-parser = { workspace = true } +wrpc-cli = { workspace = true, features = ["nats"] } +wrpc-transport = { workspace = true } +wrpc-transport-nats = { workspace = true } +wrpc-types = { workspace = true } +wrpc-interface-http = { workspace = true, features = [ + "http", + "http-body", + "hyper", + "wasmtime-wasi-http", +] } +wrpc-runtime-wasmtime = { workspace = true } diff --git a/crates/wasmtime-nats-cli/src/lib.rs b/crates/wasmtime-nats-cli/src/lib.rs new file mode 100644 index 000000000..6329b35d4 --- /dev/null +++ b/crates/wasmtime-nats-cli/src/lib.rs @@ -0,0 +1,156 @@ +use anyhow::{anyhow, bail, Context as _}; +use clap::Parser; +use tokio::fs; +use tracing::instrument; +use url::Url; +use wasmcloud_component_adapters::WASI_PREVIEW1_COMMAND_COMPONENT_ADAPTER; +use wasmtime::{ + component::{Component, Linker}, + Store, +}; +use wasmtime_wasi::bindings::Command; +use wasmtime_wasi::{ResourceTable, WasiCtxBuilder}; + +mod runtime; +use runtime::{polyfill, Ctx}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// NATS address to use + #[arg(short, long, default_value = wrpc_cli::nats::DEFAULT_URL)] + nats: Url, + + /// Prefix to listen on + prefix: String, + + /// Path or URL to Wasm command component + workload: String, +} + +pub enum Workload { + Url(Url), + Binary(Vec), +} + +#[instrument(level = "trace", ret)] +pub async fn run() -> anyhow::Result<()> { + wrpc_cli::tracing::init(); + + let Args { + nats, + prefix, + workload, + } = Args::parse(); + let nats = wrpc_cli::nats::connect(nats) + .await + .context("failed to connect to NATS")?; + + let engine = wasmtime::Engine::new( + wasmtime::Config::new() + .async_support(true) + .wasm_component_model(true), + ) + .context("failed to initialize Wasmtime engine")?; + + let wasm = if workload.starts_with('.') { + fs::read(&workload) + .await + .with_context(|| format!("failed to read relative path to workload `{workload}`")) + .map(Workload::Binary) + } else { + Url::parse(&workload) + .with_context(|| format!("failed to parse Wasm URL `{workload}`")) + .map(Workload::Url) + }?; + let wasm = match wasm { + Workload::Url(wasm) => match wasm.scheme() { + "file" => { + let wasm = wasm + .to_file_path() + .map_err(|_| anyhow!("failed to convert Wasm URL to file path"))?; + fs::read(wasm) + .await + .context("failed to read Wasm from file URL")? + } + "http" | "https" => { + let wasm = reqwest::get(wasm).await.context("failed to GET Wasm URL")?; + let wasm = wasm.bytes().await.context("failed fetch Wasm from URL")?; + wasm.to_vec() + } + scheme => bail!("URL scheme `{scheme}` not supported"), + }, + Workload::Binary(wasm) => wasm, + }; + let wasm = if wasmparser::Parser::is_core_wasm(&wasm) { + wit_component::ComponentEncoder::default() + .validate(true) + .module(&wasm) + .context("failed to set core component module")? + .adapter( + "wasi_snapshot_preview1", + WASI_PREVIEW1_COMMAND_COMPONENT_ADAPTER, + ) + .context("failed to add WASI adapter")? + .encode() + .context("failed to encode a component")? + } else { + wasm + }; + + let component = Component::new(&engine, &wasm).context("failed to compile component")?; + + let mut linker = Linker::>::new(&engine); + runtime::wasmtime_bindings::Interfaces::add_to_linker(&mut linker, |ctx| ctx) + .context("failed to link `wrpc:runtime/interfaces` interface")?; + + wasmtime_wasi::add_to_linker_async(&mut linker).context("failed to link WASI")?; + + let (resolve, world) = + match wit_component::decode(&wasm).context("failed to decode WIT component")? { + wit_component::DecodedWasm::Component(resolve, world) => (resolve, world), + wit_component::DecodedWasm::WitPackage(..) => { + bail!("binary-encoded WIT packages not currently supported") + } + }; + + let wit_parser::World { imports, .. } = resolve + .worlds + .iter() + .find_map(|(id, w)| (id == world).then_some(w)) + .context("component world missing")?; + + polyfill( + &resolve, + imports, + &engine, + &component.component_type(), + &mut linker, + ); + + let pre = linker + .instantiate_pre(&component) + .context("failed to pre-instantiate component")?; + + let mut store = Store::new( + &engine, + Ctx { + ctx: WasiCtxBuilder::new() + .inherit_env() + .inherit_stdio() + .inherit_network() + .args(&["main.wasm"]) + .build(), + table: ResourceTable::new(), + wrpc: wrpc_transport_nats::Client::new(nats, prefix), + }, + ); + let (cmd, _) = Command::instantiate_pre(&mut store, &pre) + .await + .context("failed to instantiate `command`")?; + cmd.wasi_cli_run() + .call_run(&mut store) + .await + .context("failed to run component")? + .map_err(|_| anyhow!("component failed")) +} diff --git a/crates/wasmtime-nats-cli/src/runtime.rs b/crates/wasmtime-nats-cli/src/runtime.rs new file mode 100644 index 000000000..8a6dc645c --- /dev/null +++ b/crates/wasmtime-nats-cli/src/runtime.rs @@ -0,0 +1,332 @@ +use core::iter::zip; + +use std::sync::Arc; + +use anyhow::Context as _; +use tracing::{error, instrument, trace, warn}; +use wasmtime::component::{types, Linker, Resource}; +use wasmtime_wasi::async_trait; +use wasmtime_wasi::{ResourceTable, WasiCtx, WasiView}; +use wrpc_runtime_wasmtime::{from_wrpc_value, to_wrpc_value}; +use wrpc_types::DynamicFunction; + +pub mod wasmtime_bindings { + mod keyvalue { + pub type Bucket = std::sync::Arc; + } + + wasmtime::component::bindgen!({ + world: "interfaces", + async: true, + tracing: true, + with: { + "wasi:cli": wasmtime_wasi::bindings::cli, + "wasi:clocks": wasmtime_wasi::bindings::clocks, + "wasi:filesystem": wasmtime_wasi::bindings::filesystem, + "wasi:http": wasmtime_wasi_http::bindings::http, + "wasi:io": wasmtime_wasi::bindings::io, + "wasi:keyvalue/store/bucket": keyvalue::Bucket, + "wasi:random": wasmtime_wasi::bindings::random, + "wasi:sockets": wasmtime_wasi::bindings::sockets, + }, + }); +} + +pub mod wrpc_bindings { + wit_bindgen_wrpc::generate!("interfaces-wrpc"); +} + +pub struct Ctx { + pub ctx: WasiCtx, + pub table: ResourceTable, + pub wrpc: C, +} + +impl WasiView for Ctx { + fn ctx(&mut self) -> &mut WasiCtx { + &mut self.ctx + } + fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } +} + +type Result = core::result::Result; + +trait FromWrpc { + fn from_wrpc(v: T) -> Self; +} + +impl FromWrpc + for wasmtime_bindings::wasi::keyvalue::store::Error +{ + fn from_wrpc(v: wrpc_bindings::wrpc::keyvalue::store::Error) -> Self { + match v { + wrpc_bindings::wrpc::keyvalue::store::Error::NoSuchStore => Self::NoSuchStore, + wrpc_bindings::wrpc::keyvalue::store::Error::AccessDenied => Self::AccessDenied, + wrpc_bindings::wrpc::keyvalue::store::Error::Other(s) => Self::Other(s), + } + } +} + +impl FromWrpc + for wasmtime_bindings::wasi::keyvalue::store::KeyResponse +{ + fn from_wrpc( + wrpc_bindings::wrpc::keyvalue::store::KeyResponse{ keys, cursor }: wrpc_bindings::wrpc::keyvalue::store::KeyResponse, + ) -> Self { + Self { keys, cursor } + } +} + +impl FromWrpc> for core::result::Result +where + T: FromWrpc, + E: FromWrpc, +{ + fn from_wrpc(v: Result) -> Self { + match v { + Ok(v) => Ok(T::from_wrpc(v)), + Err(v) => Err(E::from_wrpc(v)), + } + } +} + +#[async_trait] +impl wasmtime_bindings::wasi::keyvalue::store::Host for Ctx { + #[instrument(skip(self))] + async fn open( + &mut self, + name: String, + ) -> anyhow::Result>> { + let bucket = self + .table + .push(Arc::new(name)) + .context("failed to open bucket")?; + Ok(Ok(bucket)) + } +} + +#[async_trait] +impl wasmtime_bindings::wasi::keyvalue::store::HostBucket + for Ctx +{ + #[instrument(skip(self))] + async fn get( + &mut self, + bucket: Resource, + key: String, + ) -> anyhow::Result>>> { + let bucket = self.table.get(&bucket).context("failed to get bucket")?; + let v = wrpc_bindings::wrpc::keyvalue::store::get(&self.wrpc, &bucket, &key) + .await + .context("failed to invoke `wrpc:keyvalue/store.get`")?; + Ok(v.map_err(FromWrpc::from_wrpc)) + } + + #[instrument(skip(self))] + async fn set( + &mut self, + bucket: Resource, + key: String, + value: Vec, + ) -> anyhow::Result> { + let bucket = self.table.get(&bucket).context("failed to get bucket")?; + let v = wrpc_bindings::wrpc::keyvalue::store::set(&self.wrpc, &bucket, &key, &value) + .await + .context("failed to invoke `wrpc:keyvalue/store.set`")?; + Ok(v.map_err(FromWrpc::from_wrpc)) + } + + #[instrument(skip(self))] + async fn delete( + &mut self, + bucket: Resource, + key: String, + ) -> anyhow::Result> { + let bucket = self.table.get(&bucket).context("failed to get bucket")?; + let v = wrpc_bindings::wrpc::keyvalue::store::delete(&self.wrpc, &bucket, &key) + .await + .context("failed to invoke `wrpc:keyvalue/store.delete`")?; + Ok(v.map_err(FromWrpc::from_wrpc)) + } + + #[instrument(skip(self))] + async fn exists( + &mut self, + bucket: Resource, + key: String, + ) -> anyhow::Result> { + let bucket = self.table.get(&bucket).context("failed to get bucket")?; + let v = wrpc_bindings::wrpc::keyvalue::store::exists(&self.wrpc, &bucket, &key) + .await + .context("failed to invoke `wrpc:keyvalue/store.delete`")?; + Ok(v.map_err(FromWrpc::from_wrpc)) + } + + #[instrument(skip(self))] + async fn list_keys( + &mut self, + bucket: Resource, + cursor: Option, + ) -> anyhow::Result> { + let bucket = self.table.get(&bucket).context("failed to get bucket")?; + let keys = wrpc_bindings::wrpc::keyvalue::store::list_keys(&self.wrpc, &bucket, cursor) + .await + .context("failed to invoke `wrpc:keyvalue/store.list-keys`")?; + Ok(FromWrpc::from_wrpc(keys)) + } + + #[instrument(skip(self))] + fn drop( + &mut self, + bucket: Resource, + ) -> anyhow::Result<()> { + self.table + .delete(bucket) + .context("failed to delete bucket")?; + Ok(()) + } +} + +/// Polyfills all missing imports +#[instrument(level = "trace", skip_all)] +pub fn polyfill<'a, T, C>( + resolve: &wit_parser::Resolve, + imports: T, + engine: &wasmtime::Engine, + ty: &types::Component, + linker: &mut Linker>, +) where + T: IntoIterator, + T::IntoIter: ExactSizeIterator, + C: wrpc_transport::Client + Send, +{ + let imports = imports.into_iter(); + for (wk, item) in imports { + let instance_name = resolve.name_world_key(wk); + // Avoid polyfilling instances, for which static bindings are linked + match instance_name.as_ref() { + "wasi:cli/environment@0.2.0" + | "wasi:cli/exit@0.2.0" + | "wasi:cli/stderr@0.2.0" + | "wasi:cli/stdin@0.2.0" + | "wasi:cli/stdout@0.2.0" + | "wasi:cli/terminal-input@0.2.0" + | "wasi:cli/terminal-output@0.2.0" + | "wasi:cli/terminal-stderr@0.2.0" + | "wasi:cli/terminal-stdin@0.2.0" + | "wasi:cli/terminal-stdout@0.2.0" + | "wasi:clocks/monotonic-clock@0.2.0" + | "wasi:clocks/wall-clock@0.2.0" + | "wasi:filesystem/preopens@0.2.0" + | "wasi:filesystem/types@0.2.0" + | "wasi:http/incoming-handler@0.2.0" + | "wasi:http/outgoing-handler@0.2.0" + | "wasi:http/types@0.2.0" + | "wasi:io/error@0.2.0" + | "wasi:io/poll@0.2.0" + | "wasi:io/streams@0.2.0" + | "wasi:keyvalue/store@0.2.0-draft" + | "wasi:random/random@0.2.0" + | "wasi:sockets/instance-network@0.2.0" + | "wasi:sockets/network@0.2.0" + | "wasi:sockets/tcp-create-socket@0.2.0" + | "wasi:sockets/tcp@0.2.0" + | "wasi:sockets/udp-create-socket@0.2.0" + | "wasi:sockets/udp@0.2.0" => continue, + _ => {} + } + let wit_parser::WorldItem::Interface(interface) = item else { + continue; + }; + let Some(wit_parser::Interface { functions, .. }) = resolve.interfaces.get(*interface) + else { + warn!("component imports a non-existent interface"); + continue; + }; + let Some(types::ComponentItem::ComponentInstance(instance)) = + ty.get_import(engine, &instance_name) + else { + trace!( + instance_name, + "component does not import the parsed instance" + ); + continue; + }; + + let mut linker = linker.root(); + let mut linker = match linker.instance(&instance_name) { + Ok(linker) => linker, + Err(err) => { + error!( + ?err, + ?instance_name, + "failed to instantiate interface from root" + ); + continue; + } + }; + let instance_name = Arc::new(instance_name); + for (func_name, ty) in functions { + trace!( + ?instance_name, + func_name, + "polyfill component function import" + ); + let ty = match DynamicFunction::resolve(resolve, ty) { + Ok(ty) => ty, + Err(err) => { + error!(?err, "failed to resolve polyfilled function type"); + continue; + } + }; + let result_ty = match ty { + DynamicFunction::Method { results, .. } => Arc::clone(&results), + DynamicFunction::Static { results, .. } => Arc::clone(&results), + }; + let Some(types::ComponentItem::ComponentFunc(func)) = + instance.get_export(engine, func_name) + else { + trace!( + ?instance_name, + func_name, + "instance does not export the parsed function" + ); + continue; + }; + let instance_name = Arc::clone(&instance_name); + let func_name = Arc::new(func_name.to_string()); + if let Err(err) = linker.func_new_async( + Arc::clone(&func_name).as_str(), + move |mut store, params, results| { + let instance_name = Arc::clone(&instance_name); + let func_name = Arc::clone(&func_name); + let result_ty = Arc::clone(&result_ty); + let func = func.clone(); + Box::new(async move { + let params: Vec<_> = zip(params, func.params()) + .map(|(val, ty)| to_wrpc_value(&mut store, val, &ty)) + .collect::>() + .context("failed to convert wasmtime values to wRPC values")?; + let (result_values, tx) = store + .data() + .wrpc + .invoke_dynamic(&instance_name, &func_name, params, &result_ty) + .await + .context("failed to call target interface")?; + for (i, (val, ty)) in zip(result_values, func.results()).enumerate() { + let val = from_wrpc_value(&mut store, val, &ty)?; + let result = results.get_mut(i).context("invalid result vector")?; + *result = val; + } + tx.await.context("failed to transmit parameters")?; + Ok(()) + }) + }, + ) { + error!(?err, "failed to polyfill component function import"); + } + } + } +} diff --git a/crates/wasmtime-nats-cli/wit/deps.lock b/crates/wasmtime-nats-cli/wit/deps.lock new file mode 100644 index 000000000..eb174e69d --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps.lock @@ -0,0 +1,9 @@ +[wasi-keyvalue] +url = "https://github.com/WebAssembly/wasi-keyvalue/archive/main.tar.gz" +sha256 = "d2de617fe31ec0abc6072f75f97dd22bf95b3231d5b3111471d73871df9081cd" +sha512 = "6f0b4e44c684d760c54552e2bde9bc976e0a4f6525fc1d47acb98625e030847276436242f42a41f4da1bb9169fb2968c53d659d61af9b2f709f4eb6f9880e2c7" + +[wrpc-keyvalue] +url = "https://github.com/wrpc/keyvalue/archive/main.tar.gz" +sha256 = "384d54bed5a91e7673732138b9b35c85351c64abd4d359e196aaf11a97d663ed" +sha512 = "feabffd5a6b10b1043342aa7378132f2f6aace06c1d0bb67492e8ec8c23db62b2cf357db51f1672f21bb6b20e3bf8952347ce6fc2e108955e66766574e8e7793" diff --git a/crates/wasmtime-nats-cli/wit/deps.toml b/crates/wasmtime-nats-cli/wit/deps.toml new file mode 100644 index 000000000..b5f282cd0 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps.toml @@ -0,0 +1,2 @@ +wasi-keyvalue = "https://github.com/WebAssembly/wasi-keyvalue/archive/main.tar.gz" +wrpc-keyvalue = "https://github.com/wrpc/keyvalue/archive/main.tar.gz" diff --git a/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/atomic.wit b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/atomic.wit new file mode 100644 index 000000000..059efc488 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/atomic.wit @@ -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, key: string, delta: u64) -> result; +} \ No newline at end of file diff --git a/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/batch.wit b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/batch.wit new file mode 100644 index 000000000..70c05feb9 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/batch.wit @@ -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, keys: list) -> result>>>, 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, key-values: list>>) -> 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, keys: list) -> result<_, error>; +} diff --git a/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/store.wit b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/store.wit new file mode 100644 index 000000000..3354ea2f3 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/store.wit @@ -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, + /// 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 + } + + /// 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; + + /// 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>, 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) -> 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; + + /// 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) -> result; + } +} diff --git a/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/watch.wit b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/watch.wit new file mode 100644 index 000000000..ff13f7523 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/watch.wit @@ -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); + + /// 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); +} \ No newline at end of file diff --git a/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/world.wit b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/world.wit new file mode 100644 index 000000000..066148c1f --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wasi-keyvalue/world.wit @@ -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; +} \ No newline at end of file diff --git a/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/atomic.wit b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/atomic.wit new file mode 100644 index 000000000..2aa8d9206 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/atomic.wit @@ -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.{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: string, key: string, delta: u64) -> result; +} diff --git a/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/batch.wit b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/batch.wit new file mode 100644 index 000000000..29fb49da4 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/batch.wit @@ -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.{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: string, keys: list) -> result>>>, 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: string, key-values: list>>) -> 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: string, keys: list) -> result<_, error>; +} diff --git a/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/store.wit b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/store.wit new file mode 100644 index 000000000..2b66a3b3f --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/store.wit @@ -0,0 +1,114 @@ +/// 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, + /// 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 + } + + /// 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 + + /// 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(bucket: string, key: string) -> result>, 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(bucket: string, key: string, value: list) -> 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(bucket: string, 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(bucket: string, key: string) -> result; + + /// 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(bucket: string, cursor: option) -> result; +} diff --git a/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/watch.wit b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/watch.wit new file mode 100644 index 000000000..178ed5bdc --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/watch.wit @@ -0,0 +1,15 @@ +/// 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. + + /// 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: string, key: string, value: list); + + /// 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: string, key: string); +} diff --git a/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/world.wit b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/world.wit new file mode 100644 index 000000000..3320639ac --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/deps/wrpc-keyvalue/world.wit @@ -0,0 +1,26 @@ +package wrpc:keyvalue@0.2.0-draft; + +/// The `wrpc: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; +} diff --git a/crates/wasmtime-nats-cli/wit/host.wit b/crates/wasmtime-nats-cli/wit/host.wit new file mode 100644 index 000000000..a8d6edd63 --- /dev/null +++ b/crates/wasmtime-nats-cli/wit/host.wit @@ -0,0 +1,9 @@ +package wrpc:runtime@0.1.0; + +world interfaces { + import wasi:keyvalue/store@0.2.0-draft; +} + +world interfaces-wrpc { + import wrpc:keyvalue/store@0.2.0-draft; +} diff --git a/examples/go/hello-server/cmd/hello-server-nats/main.go b/examples/go/hello-server/cmd/hello-server-nats/main.go index a7ce61dd4..33419f7cb 100644 --- a/examples/go/hello-server/cmd/hello-server-nats/main.go +++ b/examples/go/hello-server/cmd/hello-server-nats/main.go @@ -17,6 +17,7 @@ import ( type Handler struct{} func (Handler) Hello(ctx context.Context) (string, error) { + slog.InfoContext(ctx, "handling `wrpc-examples:hello/handler.hello`") return "hello from Go", nil } diff --git a/examples/go/keyvalue-server/cmd/keyvalue-mem-nats/main.go b/examples/go/keyvalue-server/cmd/keyvalue-mem-nats/main.go index 218533248..f2f67c0bc 100644 --- a/examples/go/keyvalue-server/cmd/keyvalue-mem-nats/main.go +++ b/examples/go/keyvalue-server/cmd/keyvalue-mem-nats/main.go @@ -31,6 +31,7 @@ func Ok[T any](v T) *wrpc.Result[T, store.Error] { } func (h *Handler) Delete(ctx context.Context, bucket string, key string) (*wrpc.Result[struct{}, store.Error], error) { + slog.InfoContext(ctx, "handling `wrpc:keyvalue/store.delete`", "bucket", bucket, "key", key) v, ok := h.Load(bucket) if !ok { return wrpc.Err[struct{}](*errNoSuchStore), nil @@ -44,6 +45,7 @@ func (h *Handler) Delete(ctx context.Context, bucket string, key string) (*wrpc. } func (h *Handler) Exists(ctx context.Context, bucket string, key string) (*wrpc.Result[bool, store.Error], error) { + slog.InfoContext(ctx, "handling `wrpc:keyvalue/store.exists`", "bucket", bucket, "key", key) v, ok := h.Load(bucket) if !ok { return wrpc.Err[bool](*errNoSuchStore), nil @@ -52,11 +54,13 @@ func (h *Handler) Exists(ctx context.Context, bucket string, key string) (*wrpc. if !ok { return wrpc.Err[bool](*errInvalidDataType), nil } + slog.InfoContext(ctx, "delete", "bucket", bucket, "key", key) _, ok = b.Load(key) return Ok(ok), nil } func (h *Handler) Get(ctx context.Context, bucket string, key string) (*wrpc.Result[[]uint8, store.Error], error) { + slog.InfoContext(ctx, "handling `wrpc:keyvalue/store.get`", "bucket", bucket, "key", key) v, ok := h.Load(bucket) if !ok { return wrpc.Err[[]uint8](*errNoSuchStore), nil @@ -77,6 +81,7 @@ func (h *Handler) Get(ctx context.Context, bucket string, key string) (*wrpc.Res } func (h *Handler) Set(ctx context.Context, bucket string, key string, value []byte) (*wrpc.Result[struct{}, store.Error], error) { + slog.InfoContext(ctx, "handling `wrpc:keyvalue/store.set`", "bucket", bucket, "key", key, "value", value) b := &sync.Map{} v, ok := h.LoadOrStore(bucket, b) if ok { @@ -90,6 +95,7 @@ func (h *Handler) Set(ctx context.Context, bucket string, key string, value []by } func (h *Handler) ListKeys(ctx context.Context, bucket string, cursor *uint64) (*wrpc.Result[store.KeyResponse, store.Error], error) { + slog.InfoContext(ctx, "handling `wrpc:keyvalue/store.list-keys`", "bucket", bucket, "cursor", cursor) if cursor != nil { return wrpc.Err[store.KeyResponse](*store.NewError_Other("cursors are not supported")), nil } diff --git a/examples/rust/hello-component-client/.cargo/config.toml b/examples/rust/hello-component-client/.cargo/config.toml new file mode 100644 index 000000000..6b77899cb --- /dev/null +++ b/examples/rust/hello-component-client/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" diff --git a/examples/rust/hello-component-client/Cargo.toml b/examples/rust/hello-component-client/Cargo.toml new file mode 100644 index 000000000..8abb00a9c --- /dev/null +++ b/examples/rust/hello-component-client/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "hello-component-client" +version = "0.1.0" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +wit-bindgen = { workspace = true, features = ["realloc", "macros"] } diff --git a/examples/rust/hello-component-client/src/main.rs b/examples/rust/hello-component-client/src/main.rs new file mode 100644 index 000000000..a0181d922 --- /dev/null +++ b/examples/rust/hello-component-client/src/main.rs @@ -0,0 +1,8 @@ +mod bindings { + wit_bindgen::generate!(); +} + +fn main() { + let greeting = bindings::wrpc_examples::hello::handler::hello(); + println!("{greeting}"); +} diff --git a/examples/rust/hello-component-client/wit/deps.lock b/examples/rust/hello-component-client/wit/deps.lock new file mode 100644 index 000000000..275cb66c0 --- /dev/null +++ b/examples/rust/hello-component-client/wit/deps.lock @@ -0,0 +1,4 @@ +[hello] +path = "../../../wit/hello" +sha256 = "3680bb734f3fa9f7325674142a2a9b558efd34ea2cb2df7ccb651ad869078d27" +sha512 = "688fdae594dc43bd65bd15ea66b77a8f97cb4bc1c3629719e91d6c1391c66f7c8c6517d096f686cca996188f64f075c4ccb0d70a40097ce76b8b4bcc71dc7506" diff --git a/examples/rust/hello-component-client/wit/deps.toml b/examples/rust/hello-component-client/wit/deps.toml new file mode 100644 index 000000000..084f03eb0 --- /dev/null +++ b/examples/rust/hello-component-client/wit/deps.toml @@ -0,0 +1 @@ +hello = "../../../wit/hello" diff --git a/examples/rust/hello-component-client/wit/deps/hello/hello.wit b/examples/rust/hello-component-client/wit/deps/hello/hello.wit new file mode 100644 index 000000000..6c84d66cc --- /dev/null +++ b/examples/rust/hello-component-client/wit/deps/hello/hello.wit @@ -0,0 +1,13 @@ +package wrpc-examples:hello; + +interface handler { + hello: func() -> string; +} + +world client { + import handler; +} + +world server { + export handler; +} diff --git a/examples/rust/hello-component-client/wit/world.wit b/examples/rust/hello-component-client/wit/world.wit new file mode 100644 index 000000000..97aae5a62 --- /dev/null +++ b/examples/rust/hello-component-client/wit/world.wit @@ -0,0 +1,5 @@ +package wrpc-examples:hello-component-client; + +world client { + include wrpc-examples:hello/client; +} diff --git a/examples/rust/wasi-keyvalue-component-client/.cargo/config.toml b/examples/rust/wasi-keyvalue-component-client/.cargo/config.toml new file mode 100644 index 000000000..6b77899cb --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" diff --git a/examples/rust/wasi-keyvalue-component-client/Cargo.toml b/examples/rust/wasi-keyvalue-component-client/Cargo.toml new file mode 100644 index 000000000..1433f2262 --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "wasi-keyvalue-component-client" +version = "0.1.0" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = { workspace = true, features = ["std"] } +wit-bindgen = { workspace = true, features = ["realloc", "macros"] } diff --git a/examples/rust/wasi-keyvalue-component-client/src/main.rs b/examples/rust/wasi-keyvalue-component-client/src/main.rs new file mode 100644 index 000000000..a567bee78 --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/src/main.rs @@ -0,0 +1,29 @@ +mod bindings { + wit_bindgen::generate!(); +} + +use anyhow::{ensure, Context as _}; + +use bindings::wasi::keyvalue::store; + +fn main() -> anyhow::Result<()> { + let bucket = store::open("example").context("failed to open empty bucket")?; + bucket + .set(&"foo".to_string(), b"bar") + .context("failed to set `foo`")?; + let ok = bucket + .exists(&"foo".to_string()) + .context("failed to check if `foo` exists")?; + ensure!(ok); + let v = bucket + .get(&"foo".to_string()) + .context("failed to get `foo`")?; + ensure!(v.as_deref() == Some(b"bar".as_slice())); + + let store::KeyResponse { keys, cursor: _ } = + bucket.list_keys(None).context("failed to list keys")?; + for key in keys { + println!("key: {key}") + } + Ok(()) +} diff --git a/examples/rust/wasi-keyvalue-component-client/wit/deps.lock b/examples/rust/wasi-keyvalue-component-client/wit/deps.lock new file mode 100644 index 000000000..85ca79113 --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/deps.lock @@ -0,0 +1,4 @@ +[keyvalue] +url = "https://github.com/WebAssembly/wasi-keyvalue/archive/main.tar.gz" +sha256 = "d2de617fe31ec0abc6072f75f97dd22bf95b3231d5b3111471d73871df9081cd" +sha512 = "6f0b4e44c684d760c54552e2bde9bc976e0a4f6525fc1d47acb98625e030847276436242f42a41f4da1bb9169fb2968c53d659d61af9b2f709f4eb6f9880e2c7" diff --git a/examples/rust/wasi-keyvalue-component-client/wit/deps.toml b/examples/rust/wasi-keyvalue-component-client/wit/deps.toml new file mode 100644 index 000000000..dc7f77eae --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/deps.toml @@ -0,0 +1 @@ +keyvalue = "https://github.com/WebAssembly/wasi-keyvalue/archive/main.tar.gz" diff --git a/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/atomic.wit b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/atomic.wit new file mode 100644 index 000000000..059efc488 --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/atomic.wit @@ -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, key: string, delta: u64) -> result; +} \ No newline at end of file diff --git a/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/batch.wit b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/batch.wit new file mode 100644 index 000000000..70c05feb9 --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/batch.wit @@ -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, keys: list) -> result>>>, 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, key-values: list>>) -> 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, keys: list) -> result<_, error>; +} diff --git a/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/store.wit b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/store.wit new file mode 100644 index 000000000..3354ea2f3 --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/store.wit @@ -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, + /// 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 + } + + /// 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; + + /// 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>, 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) -> 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; + + /// 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) -> result; + } +} diff --git a/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/watch.wit b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/watch.wit new file mode 100644 index 000000000..ff13f7523 --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/watch.wit @@ -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); + + /// 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); +} \ No newline at end of file diff --git a/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/world.wit b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/world.wit new file mode 100644 index 000000000..066148c1f --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/deps/keyvalue/world.wit @@ -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; +} \ No newline at end of file diff --git a/examples/rust/wasi-keyvalue-component-client/wit/host.wit b/examples/rust/wasi-keyvalue-component-client/wit/host.wit new file mode 100644 index 000000000..27929709d --- /dev/null +++ b/examples/rust/wasi-keyvalue-component-client/wit/host.wit @@ -0,0 +1,5 @@ +package wrpc:runtime@0.1.0; + +world interfaces { + import wasi:keyvalue/store@0.2.0-draft; +} diff --git a/src/bin/wrpc-keyvalue-nats.rs b/src/bin/wrpc-keyvalue-nats.rs index 5efabeb17..bacbce9f5 100644 --- a/src/bin/wrpc-keyvalue-nats.rs +++ b/src/bin/wrpc-keyvalue-nats.rs @@ -1,9 +1,6 @@ use anyhow::Context as _; use clap::{Parser, Subcommand}; use tokio::io::{stdin, stdout, AsyncReadExt as _, AsyncWriteExt as _}; -use tokio::sync::mpsc; -use tracing_subscriber::layer::SubscriberExt as _; -use tracing_subscriber::util::SubscriberInitExt as _; use url::Url; mod bindings { @@ -32,7 +29,7 @@ enum Operation { #[command(author, version, about, long_about = None)] struct Args { /// NATS.io URL to connect to - #[arg(short, long, default_value = "nats://127.0.0.1:4222")] + #[arg(short, long, default_value = wrpc_cli::nats::DEFAULT_URL)] nats: Url, /// Prefix to invoke `wrpc:keyvalue` operations on @@ -45,13 +42,7 @@ struct Args { #[tokio::main] async fn main() -> anyhow::Result<()> { - tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer().compact().without_time()) - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), - ) - .init(); + wrpc_cli::tracing::init(); let Args { nats, @@ -59,7 +50,7 @@ async fn main() -> anyhow::Result<()> { operation, } = Args::parse(); - let nats = connect(nats) + let nats = wrpc_cli::nats::connect(nats) .await .context("failed to connect to NATS.io")?; let wrpc = wrpc_transport_nats::Client::new(nats, prefix); @@ -115,32 +106,3 @@ async fn main() -> anyhow::Result<()> { } Ok(()) } - -/// Connect to NATS.io server and ensure that the connection is fully established before -/// returning the resulting [`async_nats::Client`] -async fn connect(url: Url) -> anyhow::Result { - let (conn_tx, mut conn_rx) = mpsc::channel(1); - let client = async_nats::connect_with_options( - String::from(url), - async_nats::ConnectOptions::new() - .retry_on_initial_connect() - .event_callback(move |event| { - let conn_tx = conn_tx.clone(); - async move { - if let async_nats::Event::Connected = event { - conn_tx - .send(()) - .await - .expect("failed to send NATS.io server connection notification"); - } - } - }), - ) - .await - .context("failed to connect to NATS.io server")?; - conn_rx - .recv() - .await - .context("failed to await NATS.io server connection to be established")?; - Ok(client) -} diff --git a/src/bin/wrpc-wasmtime-nats.rs b/src/bin/wrpc-wasmtime-nats.rs new file mode 100644 index 000000000..4547d2118 --- /dev/null +++ b/src/bin/wrpc-wasmtime-nats.rs @@ -0,0 +1,4 @@ +#[tokio::main] +async fn main() -> anyhow::Result<()> { + wrpc_wasmtime_nats_cli::run().await +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 4530b240e..a0dc3545a 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -11,19 +11,11 @@ use tokio::process::Command; use tokio::sync::{mpsc, oneshot, OnceCell}; use tokio::task::JoinHandle; use tokio::{select, spawn}; -use tracing_subscriber::layer::SubscriberExt as _; -use tracing_subscriber::util::SubscriberInitExt as _; static INIT: OnceCell<()> = OnceCell::const_new(); async fn init_log() { - tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer().compact().without_time()) - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), - ) - .init() + wrpc_cli::tracing::init() } pub async fn init() {