diff --git a/Cargo.lock b/Cargo.lock index b69b83bc6..f5724c4bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,60 +13,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "anstream" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is-terminal", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" - -[[package]] -name = "anstyle-parse" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" -dependencies = [ - "anstyle", - "windows-sys", -] - [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "autocfg" @@ -76,9 +27,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -88,15 +39,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" - -[[package]] -name = "cc" -version = "1.0.79" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" [[package]] name = "cfg-if" @@ -104,59 +49,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clap" -version = "4.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" -dependencies = [ - "clap_builder", - "clap_derive", - "once_cell", -] - -[[package]] -name = "clap_builder" -version = "4.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" -dependencies = [ - "anstream", - "anstyle", - "bitflags 1.3.2", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - [[package]] name = "cranelift-entity" -version = "0.95.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" +version = "0.97.0" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=f7ae056a0a757492c0842ca3f2852f9a5f4bd19c#f7ae056a0a757492c0842ca3f2852f9a5f4bd19c" dependencies = [ "serde", ] @@ -170,27 +66,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -241,12 +116,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - [[package]] name = "id-arena" version = "2.2.1" @@ -274,33 +143,11 @@ dependencies = [ "serde", ] -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", -] - [[package]] name = "jco" version = "0.1.0" dependencies = [ + "anyhow", "js-component-bindgen", "wit-component", ] @@ -311,11 +158,9 @@ version = "0.1.0" dependencies = [ "anyhow", "base64", - "clap", "heck", "indexmap", "wasmtime-environ", - "wit-bindgen", "wit-component", "wit-parser", ] @@ -325,15 +170,9 @@ name = "js-component-bindgen-component" version = "0.1.0" dependencies = [ "anyhow", - "base64", - "clap", - "heck", - "indexmap", "js-component-bindgen", "wasmtime-environ", "wit-bindgen", - "wit-component", - "wit-parser", ] [[package]] @@ -342,26 +181,11 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" -[[package]] -name = "libc" -version = "0.2.141" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" - -[[package]] -name = "linux-raw-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" - [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "memchr" @@ -383,9 +207,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "percent-encoding" @@ -395,9 +219,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -415,41 +239,33 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] [[package]] -name = "rustix" -version = "0.37.11" +name = "semver" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", @@ -462,17 +278,11 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -481,9 +291,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "thiserror" @@ -537,9 +347,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -579,12 +389,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - [[package]] name = "version_check" version = "0.9.4" @@ -593,33 +397,24 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasm-encoder" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eff853c4f09eec94d76af527eddad4e9de13b11d6286a1ef7134bc30135a2b7" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77053dc709db790691d3732cfc458adc5acc881dec524965c608effdcd9c581" +checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" dependencies = [ "leb128", ] [[package]] name = "wasm-metadata" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e6532071b112df81d756fabafab8cc882b84ae3d6ce9b90f3d90c80e4abe05" +checksum = "36e5156581ff4a302405c44ca7c85347563ca431d15f1a773f12c9c7b9a6cdc9" dependencies = [ "anyhow", "indexmap", "serde", - "wasm-encoder 0.27.0", - "wasmparser 0.105.0", + "wasm-encoder", + "wasmparser", ] [[package]] @@ -627,9 +422,9 @@ name = "wasm-tools-js" version = "0.1.0" dependencies = [ "anyhow", - "wasm-encoder 0.27.0", + "wasm-encoder", "wasm-metadata", - "wasmparser 0.105.0", + "wasmparser", "wasmprinter", "wat", "wit-bindgen", @@ -639,45 +434,33 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.102.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" -dependencies = [ - "indexmap", - "url", -] - -[[package]] -name = "wasmparser" -version = "0.105.0" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83be9e0b3f9570dc1979a33ae7b89d032c73211564232b99976553e5c155ec32" +checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" dependencies = [ "indexmap", - "url", + "semver", ] [[package]] name = "wasmprinter" -version = "0.2.57" +version = "0.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b0e5ed7a74a065637f0d7798ce5f29cadb064980d24b0c82af5200122fa0d8" +checksum = "cc960b30b84abca377768f3c62cff3a1c74db8c0f6759ed581827da0bd3a3fed" dependencies = [ "anyhow", - "wasmparser 0.105.0", + "wasmparser", ] [[package]] name = "wasmtime-component-util" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e02ca7a4a3c69d72b88f26f0192e333958df6892415ac9ab84dcc42c9000c2" +version = "10.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=f7ae056a0a757492c0842ca3f2852f9a5f4bd19c#f7ae056a0a757492c0842ca3f2852f9a5f4bd19c" [[package]] name = "wasmtime-environ" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a990198cee4197423045235bf89d3359e69bd2ea031005f4c2d901125955c949" +version = "10.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=f7ae056a0a757492c0842ca3f2852f9a5f4bd19c#f7ae056a0a757492c0842ca3f2852f9a5f4bd19c" dependencies = [ "anyhow", "cranelift-entity", @@ -688,8 +471,8 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.25.0", - "wasmparser 0.102.0", + "wasm-encoder", + "wasmparser", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -697,116 +480,49 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" +version = "10.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime?rev=f7ae056a0a757492c0842ca3f2852f9a5f4bd19c#f7ae056a0a757492c0842ca3f2852f9a5f4bd19c" dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser 0.102.0", + "wasmparser", ] [[package]] name = "wast" -version = "58.0.0" +version = "60.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372eecae2d10a5091c2005b32377d7ecd6feecdf2c05838056d02d8b4f07c429" +checksum = "bd06cc744b536e30387e72a48fdd492105b9c938bb4f415c39c616a7a0a697ad" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.27.0", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d47446190e112ab1579ab40b3ad7e319d859d74e5134683f04e9f0747bf4173" +checksum = "5abe520f0ab205366e9ac7d3e6b2fc71de44e32a2b58f2ec871b6b575bdcea3b" dependencies = [ "wast", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "wit-bindgen" -version = "0.6.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=c1eb6ba2ffe303574219a3787aa1187d70d877ec#c1eb6ba2ffe303574219a3787aa1187d70d877ec" +version = "0.7.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e69cf5db8754f829637e25491c560ec0d9728852#e69cf5db8754f829637e25491c560ec0d9728852" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.3.1", "wit-bindgen-rust-macro", ] [[package]] name = "wit-bindgen-core" -version = "0.6.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=c1eb6ba2ffe303574219a3787aa1187d70d877ec#c1eb6ba2ffe303574219a3787aa1187d70d877ec" +version = "0.7.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e69cf5db8754f829637e25491c560ec0d9728852#e69cf5db8754f829637e25491c560ec0d9728852" dependencies = [ "anyhow", "wit-component", @@ -815,8 +531,8 @@ dependencies = [ [[package]] name = "wit-bindgen-rust" -version = "0.6.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=c1eb6ba2ffe303574219a3787aa1187d70d877ec#c1eb6ba2ffe303574219a3787aa1187d70d877ec" +version = "0.7.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e69cf5db8754f829637e25491c560ec0d9728852#e69cf5db8754f829637e25491c560ec0d9728852" dependencies = [ "heck", "wasm-metadata", @@ -827,8 +543,8 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-lib" -version = "0.6.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=c1eb6ba2ffe303574219a3787aa1187d70d877ec#c1eb6ba2ffe303574219a3787aa1187d70d877ec" +version = "0.7.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e69cf5db8754f829637e25491c560ec0d9728852#e69cf5db8754f829637e25491c560ec0d9728852" dependencies = [ "heck", "wit-bindgen-core", @@ -836,8 +552,8 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.6.0" -source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=c1eb6ba2ffe303574219a3787aa1187d70d877ec#c1eb6ba2ffe303574219a3787aa1187d70d877ec" +version = "0.7.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=e69cf5db8754f829637e25491c560ec0d9728852#e69cf5db8754f829637e25491c560ec0d9728852" dependencies = [ "anyhow", "proc-macro2", @@ -849,33 +565,33 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3cc435c15009f80d5e114d0dd3792959171f1fc28be2418e0f570d83b925a33" +checksum = "7cbd4c7f8f400327c482c88571f373844b7889e61460650d650fc5881bb3575c" dependencies = [ "anyhow", "bitflags 1.3.2", "indexmap", "log", - "url", - "wasm-encoder 0.27.0", + "wasm-encoder", "wasm-metadata", - "wasmparser 0.105.0", + "wasmparser", "wat", "wit-parser", ] [[package]] name = "wit-parser" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca2581061573ef6d1754983d7a9b3ed5871ef859d52708ea9a0f5af32919172" +checksum = "6daec9f093dbaea0e94043eeb92ece327bbbe70c86b1f41aca9bbfefd7f050f0" dependencies = [ "anyhow", "id-arena", "indexmap", "log", "pulldown-cmark", + "semver", "unicode-xid", "url", ] diff --git a/Cargo.toml b/Cargo.toml index 9d8b86e22..63e4c00c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,13 +5,18 @@ edition.workspace = true publish = false [[bin]] -name = "jco" -path = "bin/main.rs" +name = "self-build" +path = "bin/self_build.rs" -[build-dependencies] +[dependencies] +anyhow = { workspace = true } js-component-bindgen = { path = "./crates/js-component-bindgen" } wit-component = { workspace = true } +[build-dependencies] +anyhow = "1.0.71" +js-component-bindgen = { path = "./crates/js-component-bindgen" } +wit-component = { workspace = true } [workspace] members = ["crates/js-component-bindgen", "crates/js-component-bindgen-component", "crates/wasm-tools-component"] @@ -22,21 +27,17 @@ edition = "2021" version = "0.1.0" [workspace.dependencies] -anyhow = "1.0.69" -base64 = "0.21.0" -bitflags = "1.3.2" -clap = { version = "4.1.8", features = ["derive"] } -env_logger = "0.10.0" +anyhow = "1.0.71" +base64 = "0.21.2" heck = { version = "0.4", features = ["unicode"] } indexmap = "1.9" -pulldown-cmark = { version = "0.8", default-features = false } -wasm-encoder = "0.27.0" -wasm-metadata = "0.6.0" -wasmparser = "0.105.0" -wasmprinter = "0.2.57" -wasmtime = { version = "8.0.1", features = ["component-model"] } -wasmtime-environ = "8.0.1" -wat = "1.0.64" -wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "c1eb6ba2ffe303574219a3787aa1187d70d877ec" } -wit-component = { version = "0.9.0", features = ['dummy-module'] } -wit-parser = "0.7.1" +wasm-encoder = "0.29.0" +wasm-metadata = "0.8.0" +wasmparser = "0.107.0" +wasmprinter = "0.2.59" +wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", features = ["component-model"], rev = "f7ae056a0a757492c0842ca3f2852f9a5f4bd19c" } +wasmtime-environ = { git = "https://github.com/bytecodealliance/wasmtime", rev = "f7ae056a0a757492c0842ca3f2852f9a5f4bd19c" } +wat = "1.0.66" +wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e69cf5db8754f829637e25491c560ec0d9728852" } +wit-component = { version = "0.11.0", features = ['dummy-module'] } +wit-parser = "0.8.0" diff --git a/bin/main.rs b/bin/main.rs deleted file mode 100644 index db91fc694..000000000 --- a/bin/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - () -} diff --git a/bin/self_build.rs b/bin/self_build.rs new file mode 100644 index 000000000..d6bcb8c6d --- /dev/null +++ b/bin/self_build.rs @@ -0,0 +1,60 @@ +use std::{collections::HashMap, fs, io::Write, path::PathBuf}; + +use js_component_bindgen; +use wit_component::ComponentEncoder; + +use anyhow::Result; + +fn main() -> Result<()> { + fn transpile(component_path: &str, name: String) -> Result<()> { + let component = fs::read(&component_path).expect("wasm bindgen component missing"); + + let adapter_path = "lib/wasi_snapshot_preview1.reactor.wasm"; + let adapter = fs::read(&adapter_path).expect("preview1 adapter file missing"); + + let mut encoder = ComponentEncoder::default() + .validate(true) + .module(&component)?; + + encoder = encoder.adapter("wasi_snapshot_preview1", &adapter)?; + + let adapted_component = encoder.encode()?; + + let import_map = HashMap::from([( + "wasi:*".to_string(), + "@bytecodealliance/preview2-shim/*".to_string(), + )]); + let opts = js_component_bindgen::TranspileOpts { + name, + no_typescript: false, + instantiation: false, + map: Some(import_map), + no_nodejs_compat: false, + base64_cutoff: 5000_usize, + tla_compat: false, + valid_lifting_optimization: false, + }; + + let transpiled = js_component_bindgen::transpile(adapted_component, opts)?; + + for (filename, contents) in transpiled.files.iter() { + let outfile = PathBuf::from("./obj").join(filename); + fs::create_dir_all(outfile.parent().unwrap()).unwrap(); + let mut file = fs::File::create(outfile).unwrap(); + file.write_all(contents).unwrap(); + } + + Ok(()) + } + + transpile( + "target/wasm32-wasi/release/js_component_bindgen_component.wasm", + "js-component-bindgen-component".to_string(), + )?; + transpile( + "target/wasm32-wasi/release/wasm_tools_js.wasm", + "wasm-tools".to_string(), + )?; + + Ok(()) +} diff --git a/build.rs b/build.rs index 63a7b2452..57343d8e7 100644 --- a/build.rs +++ b/build.rs @@ -1,39 +1,41 @@ -use std::{collections::HashMap, env, fs, io::Write, path::PathBuf}; +use anyhow::Result; +use std::{env, fs, io::Write, path::PathBuf}; -fn main() { +use js_component_bindgen::{generate_types, source::wit_parser::Resolve}; + +fn main() -> Result<()> { if env::var("PREVIEW2_SHIM_TYPES").is_ok() { - let current_dir = std::env::current_dir().unwrap(); - let fixtures_dir = current_dir.join("./test/fixtures/components"); - for world in ["reactor", "proxy"] { - let component_path = fixtures_dir.join(format!("dummy_{}.component.wasm", world)); - let component = fs::read(&component_path).expect("component to be read from file"); + for world in ["proxy", "command", "reactor"] { + let name = format!("wasi-{}", world); + let preview2_wit_path = "./test/fixtures/wit/wasi"; + + let mut resolve = Resolve::default(); + let (id, _) = resolve.push_dir(&PathBuf::from(preview2_wit_path))?; + + let world = resolve.select_world(id, Some(world))?; - let import_map = HashMap::from([]); let opts = js_component_bindgen::TranspileOpts { - name: format!("wasi-{}", world), + name: "component".to_string(), no_typescript: false, - instantiation: true, - map: Some(import_map), - no_nodejs_compat: true, - base64_cutoff: 5000_usize, + no_nodejs_compat: false, + instantiation: false, + map: None, tla_compat: false, valid_lifting_optimization: false, + base64_cutoff: 0, }; - let transpiled = js_component_bindgen::transpile(component, opts) - .map_err(|e| format!("{:?}", e)) - .unwrap(); + let files = generate_types(name, resolve, world, opts)?; - for (filename, contents) in transpiled.files.iter() { - if filename.ends_with(".d.ts") { - let outfile = PathBuf::from("./packages/preview2-shim/types").join(filename); - fs::create_dir_all(outfile.parent().unwrap()).unwrap(); - let mut file = fs::File::create(outfile).unwrap(); - file.write_all(contents).unwrap(); - } + for (filename, contents) in files.iter() { + let outfile = PathBuf::from("./packages/preview2-shim/types").join(filename); + fs::create_dir_all(outfile.parent().unwrap()).unwrap(); + let mut file = fs::File::create(outfile).unwrap(); + file.write_all(contents).unwrap(); } - println!("cargo:rerun-if-changed={:?}", component_path); + println!("cargo:rerun-if-changed={:?}", preview2_wit_path); } } println!("cargo:rerun-if-changed=build.rs"); + Ok(()) } diff --git a/crates/js-component-bindgen-component/Cargo.toml b/crates/js-component-bindgen-component/Cargo.toml index a61870549..d91c5cbb3 100644 --- a/crates/js-component-bindgen-component/Cargo.toml +++ b/crates/js-component-bindgen-component/Cargo.toml @@ -11,12 +11,6 @@ crate-type = ["cdylib"] [dependencies] anyhow = { workspace = true } -heck = { workspace = true } -clap = { workspace = true, optional = true } js-component-bindgen = { path = "../js-component-bindgen" } -wasmtime-environ = { workspace = true, features = ['component-model'] } +wasmtime-environ = { workspace = true } wit-bindgen = { workspace = true } -wit-component = { workspace = true } -wit-parser = { workspace = true } -indexmap = { workspace = true } -base64 = { workspace = true } diff --git a/crates/js-component-bindgen-component/src/lib.rs b/crates/js-component-bindgen-component/src/lib.rs index f7c2574d7..760b4247c 100644 --- a/crates/js-component-bindgen-component/src/lib.rs +++ b/crates/js-component-bindgen-component/src/lib.rs @@ -1,5 +1,11 @@ +use std::path::PathBuf; + use anyhow::Result; -use js_component_bindgen::transpile; +use js_component_bindgen::{ + generate_types, + source::wit_parser::{Resolve, UnresolvedPackage}, + transpile, +}; /// Calls [`write!`] with the passed arguments and unwraps the result. /// @@ -29,27 +35,12 @@ macro_rules! uwriteln { wit_bindgen::generate!("js-component-bindgen"); -use crate::exports::*; - struct JsComponentBindgenComponent; -export_js_component_bindgen_component!(JsComponentBindgenComponent); - -// fn init() { -// static INIT: Once = Once::new(); -// INIT.call_once(|| { -// let prev_hook = std::panic::take_hook(); -// std::panic::set_hook(Box::new(move |info| { -// console::error(&info.to_string()); -// prev_hook(info); -// })); -// }); -// } +export_js_component_bindgen!(JsComponentBindgenComponent); -impl exports::Exports for JsComponentBindgenComponent { +impl JsComponentBindgen for JsComponentBindgenComponent { fn generate(component: Vec, options: GenerateOptions) -> Result { - // init(); - let opts = js_component_bindgen::TranspileOpts { name: options.name, no_typescript: options.no_typescript.unwrap_or(false), @@ -96,4 +87,48 @@ impl exports::Exports for JsComponentBindgenComponent { .collect(), }) } + + fn generate_types( + name: String, + opts: TypeGenerationOptions, + ) -> Result)>, String> { + let mut resolve = Resolve::default(); + let pkg = match opts.wit { + Wit::Source(source) => { + UnresolvedPackage::parse(&PathBuf::from(format!("{name}.wit")), &source) + .map_err(|e| e.to_string())? + } + Wit::Path(path) => { + UnresolvedPackage::parse_file(&PathBuf::from(path)).map_err(|e| e.to_string())? + } + Wit::Binary(_) => todo!(), + }; + let id = resolve.push(pkg).map_err(|e| e.to_string())?; + + let world_string = match &opts.world { + Some(world) => Some(world.to_string()), + None => None, + }; + let world = resolve + .select_world(id, world_string.as_deref()) + .map_err(|e| e.to_string())?; + + let opts = js_component_bindgen::TranspileOpts { + name: "component".to_string(), + no_typescript: false, + no_nodejs_compat: false, + instantiation: opts.instantiation.unwrap_or(false), + map: match opts.map { + Some(map) => Some(map.into_iter().collect()), + None => None, + }, + tla_compat: opts.tla_compat.unwrap_or(false), + valid_lifting_optimization: false, + base64_cutoff: 0, + }; + + let files = generate_types(name, resolve, world, opts).map_err(|e| e.to_string())?; + + Ok(files) + } } diff --git a/crates/js-component-bindgen-component/wit/js-component-bindgen.wit b/crates/js-component-bindgen-component/wit/js-component-bindgen.wit index 002f25466..032f56289 100644 --- a/crates/js-component-bindgen-component/wit/js-component-bindgen.wit +++ b/crates/js-component-bindgen-component/wit/js-component-bindgen.wit @@ -1,56 +1,77 @@ -default world js-component-bindgen-component { - export exports: interface { - type files = list>> - type maps = list> - - record generate-options { - /// Name to use for the generated component - name: string, - - /// Disables generation of `*.d.ts` files and instead only generates `*.js` - /// source files. - no-typescript: option, - - /// Provide a custom JS instantiation API for the component instead - /// of the direct importable native ESM output. - instantiation: option, - - /// Mappings of component import specifiers to JS import specifiers. - map: option, - - /// Enables all compat flags: --tla-compat. - compat: option, - - /// Disables compatibility in Node.js without a fetch global. - no-nodejs-compat: option, - - /// Set the cutoff byte size for base64 inlining core Wasm in instantiation mode - /// (set to 0 to disable all base64 inlining) - base64-cutoff: option, - - /// Enables compatibility for JS environments without top-level await support - /// via an async $init promise export to wait for instead. - tla-compat: option, - - /// Disable verification of component Wasm data structures when - /// lifting as a production optimization - valid-lifting-optimization: option, - } - - enum export-type { - function, - instance, - } - - record transpiled { - files: files, - imports: list, - exports: list> - } - - /// Generate the file structure for the transpilation of a component - /// into a JS embedding, returns the file list and imports and exports of the - /// output JS transpiled component - generate: func(component: list, options: generate-options) -> result +package local:js-component-bindgen + +world js-component-bindgen { + type files = list>> + type maps = list> + + record generate-options { + /// Name to use for the generated component + name: string, + + /// Disables generation of `*.d.ts` files and instead only generates `*.js` + /// source files. + no-typescript: option, + + /// Provide a custom JS instantiation API for the component instead + /// of the direct importable native ESM output. + instantiation: option, + + /// Mappings of component import specifiers to JS import specifiers. + map: option, + + /// Enables all compat flags: --tla-compat. + compat: option, + + /// Disables compatibility in Node.js without a fetch global. + no-nodejs-compat: option, + + /// Set the cutoff byte size for base64 inlining core Wasm in instantiation mode + /// (set to 0 to disable all base64 inlining) + base64-cutoff: option, + + /// Enables compatibility for JS environments without top-level await support + /// via an async $init promise export to wait for instead. + tla-compat: option, + + /// Disable verification of component Wasm data structures when + /// lifting as a production optimization + valid-lifting-optimization: option, } + + variant wit { + /// wit is provided as an inline WIT string + source(string), + /// wit is provided from a component binary + binary(list), + /// wit is provided from a filesystem path + path(string), + } + + record type-generation-options { + /// wit to generate typing from + wit: wit, + /// world to generate typing for + %world: option, + tla-compat: option, + instantiation: option, + map: option, + } + + enum export-type { + function, + instance, + } + + record transpiled { + files: files, + imports: list, + exports: list> + } + + /// Generate the file structure for the transpiled of a component + /// into a JS embedding, returns the file list and imports and exports of the + /// output JS generation component + export generate: func(component: list, options: generate-options) -> result + + export generate-types: func(name: string, options: type-generation-options) -> result } diff --git a/crates/js-component-bindgen/Cargo.toml b/crates/js-component-bindgen/Cargo.toml index 7f5be7f45..ef24f066a 100644 --- a/crates/js-component-bindgen/Cargo.toml +++ b/crates/js-component-bindgen/Cargo.toml @@ -16,9 +16,7 @@ transpile-bindgen = [] [dependencies] anyhow = { workspace = true } heck = { workspace = true } -clap = { workspace = true, optional = true } wasmtime-environ = { workspace = true, features = ['component-model'] } -wit-bindgen = { workspace = true } wit-component = { workspace = true } wit-parser = { workspace = true } indexmap = { workspace = true } diff --git a/crates/js-component-bindgen/src/identifier.rs b/crates/js-component-bindgen/src/identifier.rs index f607193eb..fa21fe581 100644 --- a/crates/js-component-bindgen/src/identifier.rs +++ b/crates/js-component-bindgen/src/identifier.rs @@ -12,7 +12,7 @@ pub fn is_js_identifier(s: &str) -> bool { return false; } } - return true; + NOT_IDENTIFIERS.binary_search(&s).is_err() } // https://tc39.es/ecma262/#prod-IdentifierStartChar @@ -34,3 +34,70 @@ fn is_js_identifier_char(code: char) -> bool { _ => false, }; } + +pub fn maybe_quote_id(name: &str) -> String { + if is_js_identifier(name) { + name.to_string() + } else { + format!("'{name}'") + } +} + +pub fn maybe_quote_member(name: &str) -> String { + if name == "*" { + "".to_string() + } else if is_js_identifier(name) { + format!(".{name}") + } else { + format!("['{name}']") + } +} + +const NOT_IDENTIFIERS: &'static [&'static str] = &[ + "await", + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "implements", + "import", + "in", + "instanceof", + "interface", + "let", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "static", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "yield", +]; diff --git a/crates/js-component-bindgen/src/lib.rs b/crates/js-component-bindgen/src/lib.rs index dd7af53bb..104528ed6 100644 --- a/crates/js-component-bindgen/src/lib.rs +++ b/crates/js-component-bindgen/src/lib.rs @@ -10,10 +10,10 @@ pub mod source; mod ts_bindgen; mod transpile_bindgen; +use transpile_bindgen::transpile_bindgen; pub use transpile_bindgen::TranspileOpts; use anyhow::{bail, Context}; -use heck::*; use wasmtime_environ::component::Export; use wasmtime_environ::component::{ComponentTypesBuilder, Translator}; use wasmtime_environ::wasmparser::{Validator, WasmFeatures}; @@ -21,6 +21,7 @@ use wasmtime_environ::{ScopeVec, Tunables}; use wit_component::DecodedWasm; use ts_bindgen::ts_bindgen; +use wit_parser::{Resolve, WorldId}; /// Calls [`write!`] with the passed arguments and unwraps the result. /// @@ -59,8 +60,25 @@ pub struct ComponentInfo { pub exports: Vec<(String, wasmtime_environ::component::Export)>, } +pub fn generate_types( + name: String, + resolve: Resolve, + world_id: WorldId, + opts: TranspileOpts, +) -> Result)>, anyhow::Error> { + let mut files = files::Files::default(); + + ts_bindgen(&name, &resolve, world_id, &opts, &mut files); + + let mut files_out: Vec<(String, Vec)> = Vec::new(); + for (name, source) in files.iter() { + files_out.push((name.to_string(), source.to_vec())); + } + Ok(files_out) +} + /// Generate the JS transpilation bindgen for a given Wasm component binary -/// Outputs the file map and import and export metadata for the generation +/// Outputs the file map and import and export metadata for the Transpilation #[cfg(feature = "transpile-bindgen")] pub fn transpile(component: Vec, opts: TranspileOpts) -> Result { let name = opts.name.clone(); @@ -71,8 +89,8 @@ pub fn transpile(component: Vec, opts: TranspileOpts) -> Result, opts: TranspileOpts) -> Result impt.to_string(), - None => impt.to_string(), - } - } else { - impt.to_string() - } - }) - .collect(); - - let exports = component - .exports - .iter() - .filter(|expt| { - matches!( - expt.1, - Export::Instance(_) | Export::Module(_) | Export::LiftedFunction { .. } - ) - }) - .map(|expt| (expt.0.to_lower_camel_case(), expt.1.clone())) - .collect(); - - transpile_bindgen::transpile_bindgen( + let (imports, exports) = transpile_bindgen( &name, &component, &modules, &resolve, world_id, opts, &mut files, ); diff --git a/crates/js-component-bindgen/src/transpile_bindgen.rs b/crates/js-component-bindgen/src/transpile_bindgen.rs index a725b64b5..c75b7ee2a 100644 --- a/crates/js-component-bindgen/src/transpile_bindgen.rs +++ b/crates/js-component-bindgen/src/transpile_bindgen.rs @@ -1,12 +1,13 @@ use crate::files::Files; use crate::function_bindgen::{ErrHandling, FunctionBindgen}; -use crate::identifier::is_js_identifier; +use crate::identifier::{maybe_quote_id, maybe_quote_member}; use crate::intrinsics::{render_intrinsics, Intrinsic}; use crate::source; use crate::{uwrite, uwriteln}; use base64::{engine::general_purpose, Engine as _}; use heck::*; use indexmap::IndexMap; +use std::collections::btree_map::Entry; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt::Write; use std::mem; @@ -43,7 +44,7 @@ pub struct TranspileOpts { pub valid_lifting_optimization: bool, } -struct JsBindgen { +struct JsBindgen<'a> { /// The source code for the "main" file that's going to be created for the /// component we're generating bindings for. This is incrementally added to /// over time and primarily contains the main `instantiate` function as well @@ -51,13 +52,13 @@ struct JsBindgen { src: Source, /// JS output imports map from imported specifier, to a list of bindings - imports: HashMap>, + imports: BTreeMap>, /// Core module count core_module_cnt: usize, /// Various options for code generation. - pub opts: TranspileOpts, + opts: &'a TranspileOpts, /// List of all intrinsics emitted to `src` so far. all_intrinsics: BTreeSet, @@ -71,18 +72,43 @@ pub fn transpile_bindgen( id: WorldId, opts: TranspileOpts, files: &mut Files, -) { +) -> (Vec, Vec<(String, Export)>) { let mut bindgen = JsBindgen { src: Source::default(), - imports: HashMap::new(), + imports: BTreeMap::new(), core_module_cnt: 0, - opts, + opts: &opts, all_intrinsics: BTreeSet::new(), }; bindgen.core_module_cnt = modules.len(); // bindings is the actual `instantiate` method itself, created by this // structure. + + // populate reverse map from import names to world items + let mut imports = BTreeMap::new(); + let mut exports = BTreeMap::new(); + for (key, _) in &resolve.worlds[id].imports { + let name = match key { + WorldKey::Name(name) => name.to_string(), + WorldKey::Interface(iface) => match resolve.id_of(*iface) { + Some(name) => name.to_string(), + None => continue, + }, + }; + imports.insert(name, key.clone()); + } + for (key, _) in &resolve.worlds[id].exports { + let name = match key { + WorldKey::Name(name) => name.to_string(), + WorldKey::Interface(iface) => match resolve.id_of(*iface) { + Some(name) => name.to_string(), + None => continue, + }, + }; + exports.insert(name, key.clone()); + } + let mut instantiator = Instantiator { src: Source::default(), sizes: SizeAlign::default(), @@ -92,16 +118,32 @@ pub fn transpile_bindgen( resolve, world: id, component, + imports, + exports, }; instantiator.sizes.fill(resolve); - instantiator.instantiate(); + let exports = instantiator.instantiate(); + let imports = instantiator + .imports + .keys() + .map(|key| match parse_world_key(key) { + Some((ns, package_name, _)) => { + map_import(&opts.map, &key[0..ns.len() + package_name.len() + 1]).unwrap_or_else( + || key[ns.len() + 1..ns.len() + package_name.len() + 1].to_string(), + ) + } + None => map_import(&opts.map, key).unwrap_or_else(|| key.to_string()), + }) + .collect(); instantiator.gen.src.js(&instantiator.src.js); instantiator.gen.src.js_init(&instantiator.src.js_init); bindgen.finish_component(name, files); + + (imports, exports) } -impl JsBindgen { +impl<'a> JsBindgen<'a> { fn finish_component(&mut self, name: &str, files: &mut Files) { let mut output = source::Source::default(); let mut compilation_promises = source::Source::default(); @@ -229,29 +271,6 @@ impl JsBindgen { files.push(&format!("{name}.js"), bytes); } - fn map_import(&self, impt: &str) -> String { - if let Some(map) = self.opts.map.as_ref() { - for (key, mapping) in map { - if key == impt { - return mapping.into(); - } - if let Some(wildcard_idx) = key.find('*') { - let lhs = &key[0..wildcard_idx]; - let rhs = &key[wildcard_idx + 1..]; - if impt.starts_with(lhs) && impt.ends_with(rhs) { - let matched = - &impt[wildcard_idx..wildcard_idx + impt.len() - lhs.len() - rhs.len()]; - return mapping.replace('*', matched); - } - } - } - if let Some(mapping) = map.get(impt) { - return mapping.into(); - } - } - impt.into() - } - fn intrinsic(&mut self, intrinsic: Intrinsic) -> String { self.all_intrinsics.insert(intrinsic); return intrinsic.name().to_string(); @@ -261,19 +280,21 @@ impl JsBindgen { /// Helper structure used to generate the `instantiate` method of a component. /// /// This is the main structure for parsing the output of Wasmtime. -struct Instantiator<'a> { +struct Instantiator<'a, 'b> { src: Source, - gen: &'a mut JsBindgen, + gen: &'a mut JsBindgen<'b>, modules: &'a PrimaryMap>, instances: PrimaryMap, resolve: &'a Resolve, world: WorldId, sizes: SizeAlign, component: &'a Component, + exports: BTreeMap, + imports: BTreeMap, } -impl Instantiator<'_> { - fn instantiate(&mut self) { +impl Instantiator<'_, '_> { + fn instantiate(&mut self) -> Vec<(String, Export)> { // To avoid uncaught promise rejection errors, we attach an intermediate // Promise.all with a rejection handler, if there are multiple promises. if self.modules.len() > 1 { @@ -294,10 +315,11 @@ impl Instantiator<'_> { if self.gen.opts.instantiation { let js_init = mem::take(&mut self.src.js_init); self.src.js.push_str(&js_init); - self.src.js("return "); } - self.exports(&self.component.exports); + let exports = self.exports(&self.component.exports); + + exports } fn instantiation_global_initializer(&mut self, init: &GlobalInitializer) { @@ -348,13 +370,13 @@ impl Instantiator<'_> { // for now since it can't be tested and additionally JS doesn't // support multi-memory which transcoders rely on anyway. GlobalInitializer::Transcoder(Transcoder { - index, - op, - from, - from64, - to, - to64, - signature, + index: _, + op: _, + from: _, + from64: _, + to: _, + to64: _, + signature: _, }) => unimplemented!(), } } @@ -378,18 +400,10 @@ impl Instantiator<'_> { if !import_obj.is_empty() { imports.push_str(", {\n"); for (module, names) in import_obj { - if is_js_identifier(module) { - imports.push_str(module); - } else { - uwrite!(imports, "'{module}'"); - } + imports.push_str(&maybe_quote_id(module)); imports.push_str(": {\n"); for (name, val) in names { - if is_js_identifier(name) { - imports.push_str(name); - } else { - uwrite!(imports, "'{name}'"); - } + imports.push_str(&maybe_quote_id(name)); uwriteln!(imports, ": {val},"); } imports.push_str("},\n"); @@ -413,45 +427,114 @@ impl Instantiator<'_> { // time `wit-component` only supports root-level imports of instances // where instances export functions. let (import_index, path) = &self.component.imports[import.import]; - let (import_name, _import_ty) = &self.component.import_types[*import_index]; - let func = match &self.resolve.worlds[self.world].imports[import_name.as_str()] { - WorldItem::Function(f) => { - assert_eq!(path.len(), 0); - f + let (import_name, _) = &self.component.import_types[*import_index]; + + let world_item = &self.imports[import_name]; + + let (func, import_specifier, interface_name) = match parse_world_key(import_name) { + Some((ns, package_name, export)) => { + // namespaces must either be explicitly mapped to URLs + // or, if no map entry is provided by the user, a rewrite into "kebab name" is + // automatically performed with the default convention of using the package name as the import + // and the package name and interface name as the export kebab name itself + // if there is no other export using the interface name, the interface name is exported directly as + // an additional alias + let mapped_import_name = map_import( + &self.gen.opts.map, + &import_name[0..ns.len() + package_name.len() + 1], + ); + match &self.resolve.worlds[self.world].imports[world_item] { + WorldItem::Function(f) => { + assert_eq!(path.len(), 0); + ( + f, + mapped_import_name.unwrap_or_else(|| package_name.to_string()), + "".to_string(), + ) + } + WorldItem::Interface(i) => { + assert_eq!(path.len(), 1); + let func = &self.resolve.interfaces[*i].functions[&path[0]]; + ( + func, + mapped_import_name.unwrap_or_else(|| package_name.to_string()), + format!("{package_name}-{export}"), + ) + } + WorldItem::Type(_) => unreachable!(), + } } - WorldItem::Interface(i) => { - assert_eq!(path.len(), 1); - &self.resolve.interfaces[*i].functions[&path[0]] + None => { + let mapped_import_name = map_import(&self.gen.opts.map, import_name); + match &self.resolve.worlds[self.world].imports[world_item] { + WorldItem::Function(f) => { + assert_eq!(path.len(), 0); + ( + f, + mapped_import_name.unwrap_or_else(|| import_name.to_string()), + "default".to_string(), + ) + } + WorldItem::Interface(i) => { + assert_eq!(path.len(), 1); + let func = &self.resolve.interfaces[*i].functions[&path[0]]; + ( + func, + mapped_import_name.unwrap_or_else(|| import_name.to_string()), + "*".to_string(), + ) + } + WorldItem::Type(_) => unreachable!(), + } } - WorldItem::Type(_) => unreachable!(), }; + let func_or_interface_id = (if interface_name == "*" { + &func.name + } else { + interface_name.as_str() + }) + .to_lower_camel_case(); + let index = import.index.as_u32(); let callee = format!("lowering{index}Callee"); - let import_specifier = self.gen.map_import(import_name); + let imports_map = self + .gen + .imports + .entry(import_specifier.to_string()) + .or_insert(BTreeMap::new()); - let id = func.name.to_lower_camel_case(); + let local_name = if interface_name == "*" { + callee.to_string() + } else { + format!("interface{index}") + }; + + let entry = imports_map.entry(func_or_interface_id.to_string()); + let vacant_interface = matches!(entry, Entry::Vacant(_)); + let local_name = entry.or_insert(local_name); // instance imports are otherwise hoisted if self.gen.opts.instantiation { - uwriteln!( + if vacant_interface { + uwriteln!( + self.src.js, + "const {local_name} = imports{}{};", + maybe_quote_member(&import_specifier), + maybe_quote_member(&func_or_interface_id), + ); + } + } + + // in the interface case we must lower the interface function + if interface_name != "*" { + let fn_id = func.name.to_lower_camel_case(); + uwrite!( self.src.js, - "const {callee} = imports{}.{};", - if is_js_identifier(&import_specifier) { - format!(".{}", import_specifier) - } else { - format!("['{}']", import_specifier) - }, - id + "const lowering{index}Callee = {local_name}{};", + maybe_quote_member(&fn_id) ); - } else { - let imports_vec = self - .gen - .imports - .entry(import_specifier) - .or_insert(Vec::new()); - imports_vec.push((id, callee.clone())); } uwrite!(self.src.js, "\nfunction lowering{index}"); @@ -577,29 +660,31 @@ impl Instantiator<'_> { ExportItem::Name(s) => s, }; let i = export.instance.as_u32() as usize; - if is_js_identifier(name) { - format!("exports{i}.{name}") - } else { - format!("exports{i}['{name}']") - } + format!("exports{i}{}", maybe_quote_member(name)) } - fn exports(&mut self, exports: &IndexMap) { + fn exports(&mut self, exports: &IndexMap) -> Vec<(String, Export)> { if exports.is_empty() { if self.gen.opts.instantiation { - self.src.js("{}"); + self.src.js("return {}"); } - return; - } - - if self.gen.opts.instantiation { - uwriteln!(self.src.js, "{{"); + return Vec::new(); } - let mut camel_exports = Vec::new(); - for (name, export) in exports { - let item = &self.resolve.worlds[self.world].exports[name]; - let camel = name.to_lower_camel_case(); + let mut camel_exports = BTreeMap::new(); + let mut camel_aliases = BTreeMap::new(); + for (export_name, export) in exports.iter() { + let world_key = &self.exports[export_name]; + let item = &self.resolve.worlds[self.world].exports[world_key]; + let (camel_name, maybe_camel_alias) = + if let Some((_, package_name, export)) = parse_world_key(export_name) { + ( + format!("{package_name}-{export}").to_lower_camel_case(), + Some(export.to_lower_camel_case()), + ) + } else { + (export_name.to_lower_camel_case(), None) + }; match export { Export::LiftedFunction { ty: _, @@ -607,7 +692,7 @@ impl Instantiator<'_> { options, } => { self.export_bindgen( - name, + export_name, None, func, options, @@ -617,17 +702,13 @@ impl Instantiator<'_> { }, ); } - Export::Instance(exports) => { + Export::Instance(iface) => { let id = match item { WorldItem::Interface(id) => *id, WorldItem::Function(_) | WorldItem::Type(_) => unreachable!(), }; - if self.gen.opts.instantiation { - uwriteln!(self.src.js, "{camel}: {{"); - } else { - uwriteln!(self.src.js, "const {camel} = {{"); - } - for (func_name, export) in exports { + uwriteln!(self.src.js, "const {camel_name} = {{"); + for (func_name, export) in iface { let (func, options) = match export { Export::LiftedFunction { func, options, .. } => (func, options), Export::Type(_) => continue, // ignored @@ -635,18 +716,13 @@ impl Instantiator<'_> { }; self.export_bindgen( func_name, - Some(name), + Some(export_name), func, options, &self.resolve.interfaces[id].functions[func_name], ); } - self.src.js("\n}"); - if self.gen.opts.instantiation { - self.src.js(",\n"); - } else { - self.src.js(";\n"); - } + uwriteln!(self.src.js, "\n}};"); } // ignore type exports for now @@ -655,13 +731,50 @@ impl Instantiator<'_> { // This can't be tested at this time so leave it unimplemented Export::Module(_) => unimplemented!(), } - camel_exports.push(camel); + if let Some(camel_alias) = maybe_camel_alias { + camel_aliases.insert(camel_alias, camel_name.to_string()); + } + camel_exports.insert(camel_name, export); } - if self.gen.opts.instantiation { - self.src.js("}"); - } else { - uwriteln!(self.src.js, "\nexport {{ {} }}", camel_exports.join(", ")); + // only promote aliases which aren't collisions against existing exports + for (alias, name) in &camel_aliases { + if !camel_exports.contains_key(alias) { + camel_exports.insert(alias.to_string(), camel_exports.get(name).unwrap().clone()); + } } + uwrite!( + self.src.js, + "\n{} {{ ", + if self.gen.opts.instantiation { + "return" + } else { + "export" + } + ); + uwriteln!( + self.src.js, + "{} }}", + camel_exports + .iter() + .map(|(name, _)| { + let name = name.to_string(); + if let Some(original_name) = camel_aliases.get(&name) { + if self.gen.opts.instantiation { + format!("{name}: {original_name}") + } else { + format!("{original_name} as {name}") + } + } else { + name + } + }) + .collect::>() + .join(", ") + ); + camel_exports + .iter() + .map(|(name, &import)| (name.clone(), import.clone())) + .collect() } fn export_bindgen( @@ -673,7 +786,7 @@ impl Instantiator<'_> { func: &Function, ) { let name = name.to_lower_camel_case(); - if self.gen.opts.instantiation || instance_name.is_some() { + if instance_name.is_some() { self.src.js.push_str(&name); } else { uwrite!(self.src.js, "\nfunction {name}"); @@ -686,7 +799,7 @@ impl Instantiator<'_> { func, AbiVariant::GuestExport, ); - if self.gen.opts.instantiation || instance_name.is_some() { + if instance_name.is_some() { self.src.js(",\n"); } else { self.src.js("\n"); @@ -709,6 +822,42 @@ impl Source { } } +fn map_import(map: &Option>, impt: &str) -> Option { + if let Some(map) = map.as_ref() { + for (key, mapping) in map { + if key == impt { + return Some(mapping.into()); + } + if let Some(wildcard_idx) = key.find('*') { + let lhs = &key[0..wildcard_idx]; + let rhs = &key[wildcard_idx + 1..]; + if impt.starts_with(lhs) && impt.ends_with(rhs) { + let matched = + &impt[wildcard_idx..wildcard_idx + impt.len() - lhs.len() - rhs.len()]; + return Some(mapping.replace('*', matched)); + } + } + } + if let Some(mapping) = map.get(impt) { + return Some(mapping.into()); + } + } + None +} + +pub fn parse_world_key<'a>(name: &'a str) -> Option<(&'a str, &'a str, &'a str)> { + let registry_idx = match name.find(':') { + Some(idx) => idx, + None => return None, + }; + let ns = &name[0..registry_idx]; + match name.rfind('/') { + Some(sep_idx) => Some((ns, &name[registry_idx + 1..sep_idx], &name[sep_idx + 1..])), + // interface is a namespace, function is a default export + None => Some((ns, &name[registry_idx + 1..], "".as_ref())), + } +} + fn core_file_name(name: &str, idx: u32) -> String { let i_str = if idx == 0 { String::from("") diff --git a/crates/js-component-bindgen/src/ts_bindgen.rs b/crates/js-component-bindgen/src/ts_bindgen.rs index 71a2ce8a2..57921cbc4 100644 --- a/crates/js-component-bindgen/src/ts_bindgen.rs +++ b/crates/js-component-bindgen/src/ts_bindgen.rs @@ -1,9 +1,12 @@ use crate::files::Files; use crate::function_bindgen::{array_ty, as_nullable, maybe_null}; +use crate::identifier::maybe_quote_id; use crate::source::Source; -use crate::transpile_bindgen::TranspileOpts; +use crate::transpile_bindgen::{parse_world_key, TranspileOpts}; use crate::uwriteln; use heck::*; +use std::collections::btree_map::Entry; +use std::collections::{BTreeMap, HashSet}; use std::fmt::Write; use std::mem; use wit_parser::abi::AbiVariant; @@ -51,28 +54,91 @@ pub fn ts_bindgen( let world = &resolve.worlds[id]; bindgen.preprocess(resolve, &world.name); - let mut funcs = Vec::new(); - - for (name, import) in world.imports.iter() { - match import { - WorldItem::Function(f) => funcs.push((name.as_str(), f)), - WorldItem::Interface(id) => bindgen.import_interface(resolve, name, *id, files), - WorldItem::Type(_) => unimplemented!("type imports"), + { + let mut funcs = Vec::new(); + let mut imports = BTreeMap::new(); + for (name, import) in world.imports.iter() { + match import { + WorldItem::Function(f) => funcs.push(f), + WorldItem::Interface(id) => match name { + WorldKey::Name(name) => { + // kebab name -> direct ns namespace import + bindgen.import_interface(resolve, name, *id, files); + } + // namespaced ns:pkg/iface + // -> group by pkg import by convention + WorldKey::Interface(id) => { + let import_name = resolve.id_of(*id).unwrap(); + let (_, pkg, iface) = parse_world_key(&import_name).unwrap(); + let specifier_id = pkg.to_string(); + match imports.entry(specifier_id) { + Entry::Vacant(gap) => { + gap.insert(vec![(format!("{}-{}", pkg, iface), id)]); + } + Entry::Occupied(ref mut entry) => { + entry.get_mut().push((format!("{}-{}", pkg, iface), id)); + } + } + } + }, + WorldItem::Type(_) => {} + } + } + // kebab import funcs (always default imports) + if !funcs.is_empty() { + bindgen.import_funcs(resolve, id, &funcs, files); + } + // namespace imports are grouped by namespace / kebab name + // kebab name imports are direct + for (name, import_interfaces) in imports { + bindgen.import_interfaces(resolve, name.as_ref(), import_interfaces, files); } } - if !funcs.is_empty() { - bindgen.import_funcs(resolve, id, &funcs, files); - } - funcs.clear(); + + let mut funcs = Vec::new(); + let mut seen_names = HashSet::new(); + let mut export_aliases: Vec<(String, String)> = Vec::new(); for (name, export) in world.exports.iter() { + let name = match name { + WorldKey::Name(name) => name.to_string(), + WorldKey::Interface(interface) => { + let export_name = resolve.id_of(*interface).unwrap(); + let (_, pkg, iface) = parse_world_key(&export_name).unwrap(); + let out_name = format!("{}-{}", pkg, iface); + export_aliases.push((iface.to_lower_camel_case(), out_name.to_string())); + out_name + } + }; + seen_names.insert(name.to_string()); match export { - WorldItem::Function(f) => funcs.push((name.as_str(), f)), - WorldItem::Interface(id) => bindgen.export_interface(resolve, name, *id, files), + WorldItem::Function(f) => funcs.push((name, f)), + WorldItem::Interface(id) => { + bindgen.export_interface(resolve, &name, *id, files, opts.instantiation); + } WorldItem::Type(_) => unimplemented!("type exports"), } } + for (alias, name) in export_aliases { + if !seen_names.contains(&alias) { + if opts.instantiation { + uwriteln!( + bindgen.export_object, + "{}: typeof {}Exports,", + alias, + name.to_upper_camel_case() + ); + } else { + uwriteln!( + bindgen.export_object, + "export const {}: typeof {}Exports;", + alias, + name.to_upper_camel_case() + ); + } + } + } if !funcs.is_empty() { - let end_character = if opts.instantiation { "," } else { ";" }; + let end_character = if opts.instantiation { ',' } else { ';' }; bindgen.export_funcs(resolve, id, &funcs, files, end_character); } @@ -163,19 +229,50 @@ impl TsBindgen { AbiVariant::GuestImport, ); let camel = name.to_upper_camel_case(); - uwriteln!(self.import_object, "'{name}': typeof {camel}Imports,"); + uwriteln!( + self.import_object, + "{}: typeof {camel}Imports,", + maybe_quote_id(name) + ); + } + fn import_interfaces( + &mut self, + resolve: &Resolve, + import_name: &str, + ifaces: Vec<(String, &InterfaceId)>, + files: &mut Files, + ) { + uwriteln!(self.import_object, "{}: {{", maybe_quote_id(import_name),); + for (name, &id) in ifaces { + self.generate_interface( + &name, + resolve, + id, + "imports", + "Imports", + files, + AbiVariant::GuestImport, + ); + let camel = name.to_upper_camel_case(); + uwriteln!( + self.import_object, + "{}: typeof {camel}Imports,", + name.to_lower_camel_case() + ); + } + uwriteln!(self.import_object, "}},"); } fn import_funcs( &mut self, resolve: &Resolve, _world: WorldId, - funcs: &[(&str, &Function)], + funcs: &[&Function], _files: &mut Files, ) { let mut gen = self.js_interface(resolve); - for (_, func) in funcs { - gen.ts_func(func, AbiVariant::GuestImport, ","); + for func in funcs { + gen.ts_func(func, AbiVariant::GuestImport, true, "", ','); } gen.gen.import_object.push_str(&gen.src); } @@ -186,6 +283,7 @@ impl TsBindgen { name: &str, id: InterfaceId, files: &mut Files, + instantiation: bool, ) { self.generate_interface( name, @@ -197,23 +295,37 @@ impl TsBindgen { AbiVariant::GuestExport, ); let camel = name.to_upper_camel_case(); - uwriteln!(self.export_object, "'{name}': typeof {camel}Exports,"); + if instantiation { + uwriteln!( + self.export_object, + "{}: typeof {camel}Exports,", + name.to_lower_camel_case() + ); + } else { + uwriteln!( + self.export_object, + "export const {}: typeof {camel}Exports;", + name.to_lower_camel_case() + ); + } } fn export_funcs( &mut self, resolve: &Resolve, _world: WorldId, - funcs: &[(&str, &Function)], + funcs: &[(String, &Function)], _files: &mut Files, - end_character: &str, + end_character: char, ) { let mut gen = self.js_interface(resolve); + let prefix = if end_character == ';' { + "export function " + } else { + "" + }; for (_, func) in funcs { - if end_character == ";" { - gen.src.push_str("export function "); - } - gen.ts_func(func, AbiVariant::GuestExport, end_character); + gen.ts_func(func, AbiVariant::GuestExport, false, prefix, end_character); } gen.gen.export_object.push_str(&gen.src); @@ -238,9 +350,9 @@ impl TsBindgen { let mut gen = self.js_interface(resolve); uwriteln!(gen.src, "export namespace {camel} {{"); + let prefix = "export function "; for (_, func) in resolve.interfaces[id].functions.iter() { - gen.src.push_str("export function "); - gen.ts_func(func, abi, ";"); + gen.ts_func(func, abi, false, prefix, ';'); } uwriteln!(gen.src, "}}"); @@ -397,10 +509,23 @@ impl<'a> TsInterface<'a> { self.src.push_str("]"); } - fn ts_func(&mut self, func: &Function, abi: AbiVariant, end_character: &str) { + fn ts_func( + &mut self, + func: &Function, + abi: AbiVariant, + default: bool, + prefix: &str, + end_character: char, + ) { self.docs(&func.docs); - self.src.push_str(&func.item_name().to_lower_camel_case()); + self.src.push_str(prefix); + + if default { + self.src.push_str("default"); + } else { + self.src.push_str(&func.item_name().to_lower_camel_case()); + } self.src.push_str("("); let param_start = match &func.kind { @@ -411,7 +536,7 @@ impl<'a> TsInterface<'a> { if i > 0 { self.src.push_str(", "); } - self.src.push_str(to_js_ident(&name.to_lower_camel_case())); + self.src.push_str(&name.to_lower_camel_case()); self.src.push_str(": "); self.print_ty( ty, @@ -475,7 +600,7 @@ impl<'a> TsInterface<'a> { as_nullable(self.resolve, &field.ty).map_or(("", &field.ty), |ty| ("?", ty)); self.src.push_str(&format!( "{}{}: ", - field.name.to_lower_camel_case(), + maybe_quote_id(&field.name.to_lower_camel_case()), option_str )); self.print_ty(ty, Mode::Lift); @@ -665,11 +790,3 @@ impl<'a> TsInterface<'a> { self.src.push_str(";\n"); } } - -fn to_js_ident(name: &str) -> &str { - match name { - "in" => "in_", - "import" => "import_", - s => s, - } -} diff --git a/crates/wasm-tools-component/src/lib.rs b/crates/wasm-tools-component/src/lib.rs index 544dbad8a..96ba864f9 100644 --- a/crates/wasm-tools-component/src/lib.rs +++ b/crates/wasm-tools-component/src/lib.rs @@ -2,16 +2,14 @@ use std::collections::VecDeque; use std::path::PathBuf; use wasm_encoder::{Encode, Section}; use wasm_metadata::Producers; -use wit_component::{ComponentEncoder, DecodedWasm, DocumentPrinter, StringEncoding}; +use wit_component::{ComponentEncoder, DecodedWasm, WitPrinter}; use wit_parser::{Resolve, UnresolvedPackage}; wit_bindgen::generate!("wasm-tools"); struct WasmToolsJs; -export_wasm_tools_js!(WasmToolsJs); - -use crate::exports::*; +export_wasm_tools!(WasmToolsJs); // fn init() { // static INIT: Once = Once::new(); @@ -24,7 +22,7 @@ use crate::exports::*; // }); // } -impl exports::Exports for WasmToolsJs { +impl WasmTools for WasmToolsJs { fn parse(wat: String) -> Result, String> { // init(); @@ -63,27 +61,27 @@ impl exports::Exports for WasmToolsJs { Ok(bytes) } - fn component_wit(binary: Vec, name: Option) -> Result { + fn component_wit(binary: Vec) -> Result { // init(); - let decoded = wit_component::decode(&name.unwrap_or(String::from("component")), &binary) + let decoded = wit_component::decode(&binary) .map_err(|e| format!("Failed to decode wit component\n{:?}", e))?; // let world = decode_world("component", &binary); let doc = match &decoded { DecodedWasm::WitPackage(_resolve, _pkg) => panic!("Unexpected wit package"), - DecodedWasm::Component(resolve, world) => resolve.worlds[*world].document, + DecodedWasm::Component(resolve, world) => resolve.worlds[*world].package.unwrap(), }; - let output = DocumentPrinter::default() + let output = WitPrinter::default() .print(decoded.resolve(), doc) .map_err(|e| format!("Unable to print wit\n${:?}", e))?; Ok(output) } - fn component_embed(embed_opts: exports::EmbedOpts) -> Result, String> { + fn component_embed(embed_opts: EmbedOpts) -> Result, String> { let binary = &embed_opts.binary; let mut resolve = Resolve::default(); @@ -96,9 +94,7 @@ impl exports::Exports for WasmToolsJs { UnresolvedPackage::parse_file(&PathBuf::from(wit_path)).map_err(|e| e.to_string())? }; - let id = resolve - .push(pkg, &Default::default()) - .map_err(|e| e.to_string())?; + let id = resolve.push(pkg).map_err(|e| e.to_string())?; let world_string = match &embed_opts.world { Some(world) => Some(world.to_string()), @@ -109,14 +105,14 @@ impl exports::Exports for WasmToolsJs { .map_err(|e| e.to_string())?; let string_encoding = match &embed_opts.string_encoding { - None | Some(exports::StringEncoding::Utf8) => StringEncoding::UTF8, - Some(exports::StringEncoding::Utf16) => StringEncoding::UTF16, - Some(exports::StringEncoding::CompactUtf16) => StringEncoding::CompactUTF16, + None | Some(StringEncoding::Utf8) => wit_component::StringEncoding::UTF8, + Some(StringEncoding::Utf16) => wit_component::StringEncoding::UTF16, + Some(StringEncoding::CompactUtf16) => wit_component::StringEncoding::CompactUTF16, }; let mut core_binary = if matches!( &embed_opts, - exports::EmbedOpts { + EmbedOpts { dummy: Some(true), .. } diff --git a/crates/wasm-tools-component/wit/wasm-tools.wit b/crates/wasm-tools-component/wit/wasm-tools.wit index b37fb1d81..8eeb2d011 100644 --- a/crates/wasm-tools-component/wit/wasm-tools.wit +++ b/crates/wasm-tools-component/wit/wasm-tools.wit @@ -1,58 +1,58 @@ -default world wasm-tools-js { - export exports: interface { - /// Translate the WebAssembly text format to binary - parse: func(wat: string) -> result, string> - - /// Translate the WebAssembly binary format to text - print: func(binary: list) -> result - - enum string-encoding { - utf8, - utf16, - compact-utf16 - } - - /// Create a component from a core wasm binary that implements and embeds a component type - component-new: func(binary: list, adapters: option>>>) -> result, string> - - /// Extract a *.wit interface from a component, optionally providing a document name to extract - component-wit: func(binary: list, document: option) -> result - - type producers-fields = list>>> - - /// Embed a WIT type into a component. - /// Only a singular WIT document without use resolutions is supported for this API. - record embed-opts { - binary: option>, - /// Pass an inline WIT source - wit-source: option, - /// Pass the file system path to WIT file - wit-path: option, - string-encoding: option, - dummy: option, - %world: option, - metadata: option - } - - component-embed: func(embed-opts: embed-opts) -> result, string> - - variant module-meta-type { - module, - // the number of nested modules - component(u32), - } - - record module-metadata { - name: option, - meta-type: module-meta-type, - range: tuple, - producers: producers-fields, - } - - /// Extract the metadata for a component - metadata-show: func(binary: list) -> result, string> - - /// Append producer metadata to a component - metadata-add: func(binary: list, metadata: producers-fields) -> result, string> +package local:wasm-tools + +world wasm-tools { + /// Translate the WebAssembly text format to binary + export parse: func(wat: string) -> result, string> + + /// Translate the WebAssembly binary format to text + export print: func(binary: list) -> result + + enum string-encoding { + utf8, + utf16, + compact-utf16 + } + + /// Create a component from a core wasm binary that implements and embeds a component type + export component-new: func(binary: list, adapters: option>>>) -> result, string> + + /// Extract a *.wit interface from a component, optionally providing a document name to extract + export component-wit: func(binary: list) -> result + + type producers-fields = list>>> + + /// Embed a WIT type into a component. + /// Only a singular WIT document without use resolutions is supported for this API. + record embed-opts { + binary: option>, + /// Pass an inline WIT source + wit-source: option, + /// Pass the file system path to WIT file + wit-path: option, + string-encoding: option, + dummy: option, + %world: option, + metadata: option + } + + export component-embed: func(embed-opts: embed-opts) -> result, string> + + variant module-meta-type { + module, + // the number of nested modules + component(u32), + } + + record module-metadata { + name: option, + meta-type: module-meta-type, + range: tuple, + producers: producers-fields, } + + /// Extract the metadata for a component + export metadata-show: func(binary: list) -> result, string> + + /// Append producer metadata to a component + export metadata-add: func(binary: list, metadata: producers-fields) -> result, string> } diff --git a/lib/wasi_preview1_component_adapter.command.wasm b/lib/wasi_preview1_component_adapter.command.wasm deleted file mode 100644 index 956ba52fd..000000000 Binary files a/lib/wasi_preview1_component_adapter.command.wasm and /dev/null differ diff --git a/lib/wasi_preview1_component_adapter.reactor.wasm b/lib/wasi_preview1_component_adapter.reactor.wasm deleted file mode 100644 index c85dbc7e3..000000000 Binary files a/lib/wasi_preview1_component_adapter.reactor.wasm and /dev/null differ diff --git a/lib/wasi_snapshot_preview1.command.wasm b/lib/wasi_snapshot_preview1.command.wasm new file mode 100644 index 000000000..1c265417d Binary files /dev/null and b/lib/wasi_snapshot_preview1.command.wasm differ diff --git a/lib/wasi_snapshot_preview1.reactor.wasm b/lib/wasi_snapshot_preview1.reactor.wasm new file mode 100644 index 000000000..5d4017c75 Binary files /dev/null and b/lib/wasi_snapshot_preview1.reactor.wasm differ diff --git a/package.json b/package.json index 8bf7b5f67..3efa0ea56 100644 --- a/package.json +++ b/package.json @@ -24,18 +24,8 @@ "typescript": "^4.3.2" }, "scripts": { - "build": "npm run build:set-last && npm run build:dev && ./build-dist.sh", - "build:self": "npm run build:set-self && npm run build:dev && ./build-dist.sh", - "build:set-last": "echo './node_modules/.bin/jco $@' > jco.sh && chmod +x jco.sh", - "build:set-self": "echo './src/jco.js $@' > jco.sh && chmod +x jco.sh", - "build:dev": "npm run build:wasm && mkdir -p obj && npm run build:js-component-bindgen-component && npm run build:wasm-tools", - "build:wasm": "cargo build --workspace --target wasm32-wasi --release", - "build:js-component-bindgen-component": "npm run build:component:js-component-bindgen-component && npm run build:transpile:js-component-bindgen-component", - "build:wasm-tools": "npm run build:component:wasm-tools && npm run build:transpile:wasm-tools", - "build:component:js-component-bindgen-component": "./jco.sh new target/wasm32-wasi/release/js_component_bindgen_component.wasm -o obj/js-component-bindgen-component.wasm --adapt wasi_snapshot_preview1=lib/wasi_preview1_component_adapter.reactor.wasm", - "build:component:wasm-tools": "./jco.sh new target/wasm32-wasi/release/wasm_tools_js.wasm -o obj/wasm-tools.wasm --adapt wasi_snapshot_preview1=lib/wasi_preview1_component_adapter.reactor.wasm", - "build:transpile:js-component-bindgen-component": "./jco.sh transpile obj/js-component-bindgen-component.wasm --out-dir obj", - "build:transpile:wasm-tools": "./jco.sh transpile obj/wasm-tools.wasm --out-dir obj", + "build": "npm run build:dev && ./build-dist.sh", + "build:dev": "cargo build --workspace --target wasm32-wasi --release && cargo run", "build:types:preview2-shim": "PREVIEW2_SHIM_TYPES=* cargo build -p jco", "lint": "eslint -c eslintrc.cjs lib/**/*.js packages/*/lib/**/*.js", "test": "mocha -u tdd test/test.js --timeout 120000", diff --git a/packages/preview2-shim/README.md b/packages/preview2-shim/README.md index 564c8e14c..9eb0e1ec7 100644 --- a/packages/preview2-shim/README.md +++ b/packages/preview2-shim/README.md @@ -16,6 +16,7 @@ Currently supports Node.js and browser versions, but alternative implementations | Poll | :x: | :x: | | Random | :heavy_check_mark: | :heavy_check_mark: | | Sockets | :x: | :x: | +| CLI Base | :heavy_check_mark: | :heavy_check_mark: | # License diff --git a/packages/preview2-shim/lib/browser/cli-base.js b/packages/preview2-shim/lib/browser/cli-base.js new file mode 100644 index 000000000..6c83cfd5f --- /dev/null +++ b/packages/preview2-shim/lib/browser/cli-base.js @@ -0,0 +1,57 @@ +let _env; +export function _setEnv (envObj) { + _env = Object.entries(envObj); +} + +export const cliBaseEnvironment = { + getEnvironment () { + if (!_env) _env = []; + return _env; + } +}; + +class ComponentExit extends Error { + constructor(code) { + super(`Component exited ${code === 0 ? 'successfully' : 'with error'}`); + this.code = code; + } +} + +export const cliBaseExit = { + exit (status) { + throw new ComponentExit(status.tag === 'err' ? 1 : 0); + } +}; + +export const cliBasePreopens = { + getDirectories () { + return []; + } +} + +export const cliBaseStdin = { + getStdin () { + return 0; + } +}; + +export const cliBaseStdout = { + getStdout () { + return 1; + } +}; + +export const cliBaseStderr = { + getStderr () { + return 2; + } +}; + +export { + cliBaseEnvironment as environment, + cliBaseExit as exit, + cliBasePreopens as preopens, + cliBaseStdin as stdin, + cliBaseStdout as stdout, + cliBaseStderr as stderr +} diff --git a/packages/preview2-shim/lib/browser/clocks.js b/packages/preview2-shim/lib/browser/clocks.js new file mode 100644 index 000000000..4db96fa0d --- /dev/null +++ b/packages/preview2-shim/lib/browser/clocks.js @@ -0,0 +1,50 @@ +function _hrtimeBigint () { + return BigInt(Math.floor(performance.now() * 1e9)); +} + +let _hrStart = _hrtimeBigint(); + +export const clocksMonotonicClock = { + resolution() { + return 1n; + }, + now () { + return _hrtimeBigint() - _hrStart; + }, + subscribe (_when, _absolute) { + console.log(`[monotonic-clock] Subscribe`); + } +}; + +export const clocksTimezone = { + display (timezone, when) { + console.log(`[timezone] DISPLAY ${timezone} ${when}`); + }, + + utcOffset (timezone, when) { + console.log(`[timezone] UTC OFFSET ${timezone} ${when}`); + return 0; + }, + + dropTimezone (timezone) { + console.log(`[timezone] DROP ${timezone}`); + } +}; + +export const clocksWallClock = { + now() { + const seconds = BigInt(Math.floor(Date.now() / 1e3)); + const nanoseconds = (Date.now() % 1e3) * 1e6; + return { seconds, nanoseconds }; + }, + + resolution() { + console.log(`[wall-clock] Wall clock resolution`); + } +}; + +export { + clocksMonotonicClock as monotonicClock, + clocksTimezone as timezone, + clocksWallClock as wallClock +} diff --git a/packages/preview2-shim/lib/browser/console.js b/packages/preview2-shim/lib/browser/console.js deleted file mode 100644 index 2d3f0890a..000000000 --- a/packages/preview2-shim/lib/browser/console.js +++ /dev/null @@ -1,12 +0,0 @@ -const levels = ["trace", "debug", "info", "warn", "error"]; - -let logLevel = levels.indexOf("warn"); - -export function setLevel(level) { - logLevel = levels.indexOf(level); -} - -export function log(level, context, msg) { - if (logLevel > levels.indexOf(level)) return; - console[level](`(${context}) ${msg}\n`); -} diff --git a/packages/preview2-shim/lib/browser/default-outgoing-HTTP.js b/packages/preview2-shim/lib/browser/default-outgoing-HTTP.js deleted file mode 100644 index bb24fa000..000000000 --- a/packages/preview2-shim/lib/browser/default-outgoing-HTTP.js +++ /dev/null @@ -1,3 +0,0 @@ -export function handle(_request, _options) { - console.log("[default-outgoing-HTTP] Handle"); -} diff --git a/packages/preview2-shim/lib/browser/environment.js b/packages/preview2-shim/lib/browser/environment.js deleted file mode 100644 index 253a41a49..000000000 --- a/packages/preview2-shim/lib/browser/environment.js +++ /dev/null @@ -1,17 +0,0 @@ -let _env; -export function _setEnv (envObj) { - _env = Object.entries(envObj); -} - -export function getEnvironment () { - if (!_env) _env = []; - return _env; -} - -export function preopens () { - return []; -} - -export function getArguments () { - return []; -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/browser/exit.js b/packages/preview2-shim/lib/browser/exit.js deleted file mode 100644 index 5509bdc65..000000000 --- a/packages/preview2-shim/lib/browser/exit.js +++ /dev/null @@ -1,21 +0,0 @@ -class FailureExit extends Error { - code = 1; - constructor() { - super("failure-exit"); - } -} - -class SuccessfulExit extends Error { - code = 0; - constructor() { - super("successful-exit"); - } -} - -export function exit(status) { - console.log(`[exit] Exit: ${JSON.stringify(status)}`); - if (status.tag === "err") { - throw new FailureExit(); - } - throw new SuccessfulExit(); -} diff --git a/packages/preview2-shim/lib/browser/filesystem.js b/packages/preview2-shim/lib/browser/filesystem.js index a3f848b0f..4c6621b63 100644 --- a/packages/preview2-shim/lib/browser/filesystem.js +++ b/packages/preview2-shim/lib/browser/filesystem.js @@ -1,150 +1,143 @@ -// export interface DescriptorStat { -// dev: Device, -// ino: Inode, -// type: DescriptorType, -// nlink: Linkcount, -// size: Filesize, -// atim: Timestamp, -// mtim: Timestamp, -// ctim: Timestamp, -// } - -export function readViaStream(fd, offset) { - console.log(`[filesystem] READ STREAM ${fd} ${offset}`); -} - -export function writeViaStream(fd, offset) { - console.log(`[filesystem] WRITE STREAM ${fd} ${offset}`); -} - -export function appendViaStream(fd) { - console.log(`[filesystem] APPEND STREAM ${fd}`); -} - -export function advise(fd, offset, length, advice) { - console.log(`[filesystem] ADVISE`, fd, offset, length, advice); -} - -export function syncData(fd) { - console.log(`[filesystem] SYNC DATA ${fd}`); -} - -export function getFlags(fd) { - console.log(`[filesystem] FLAGS FOR ${fd}`); -} - -export function getType(fd) { - console.log(`[filesystem] GET TYPE ${fd}`); -} - -export function setFlags(fd, flags) { - console.log(`[filesystem] SET FLAGS ${fd} ${JSON.stringify(flags)}`); -} - -export function setSize(fd, size) { - console.log(`[filesystem] SET SIZE`, fd, size); -} - -export function setTimes(fd, dataAccessTimestamp, dataModificationTimestamp) { - console.log(`[filesystem] SET TIMES`, fd, dataAccessTimestamp, dataModificationTimestamp); -} - -export function read(fd, length, offset) { - console.log(`[filesystem] READ`, fd, length, offset); -} - -export function write(fd, buffer, offset) { - console.log(`[filesystem] WRITE`, fd, buffer, offset); -} - -export function readDirectory(fd) { - console.log(`[filesystem] READ DIR`, fd); -} - -export function sync(fd) { - console.log(`[filesystem] SYNC`, fd); -} - -export function createDirectoryAt(fd, path) { - console.log(`[filesystem] CREATE DIRECTORY`, fd, path); -} - -export function stat(fd) { - console.log(`[filesystem] STAT`, fd); -} - -export function statAt(fd, pathFlags, path) { - console.log(`[filesystem] STAT`, fd, pathFlags, path); -} - -export function setTimesAt(fd) { - console.log(`[filesystem] SET TIMES AT`, fd); -} - -export function linkAt(fd) { - console.log(`[filesystem] LINK AT`, fd); -} - -export function openAt(fd) { - console.log(`[filesystem] OPEN AT`, fd); -} - -export function readlinkAt(fd) { - console.log(`[filesystem] READLINK AT`, fd); -} - -export function removeDirectoryAt(fd) { - console.log(`[filesystem] REMOVE DIR AT`, fd); -} - -export function renameAt(fd) { - console.log(`[filesystem] RENAME AT`, fd); -} - -export function symlinkAt(fd) { - console.log(`[filesystem] SYMLINK AT`, fd); -} - -export function unlinkFileAt(fd) { - console.log(`[filesystem] UNLINK FILE AT`, fd); -} +export const filesystem = { + readViaStream(fd, offset) { + console.log(`[filesystem] READ STREAM ${fd} ${offset}`); + }, + + writeViaStream(fd, offset) { + console.log(`[filesystem] WRITE STREAM ${fd} ${offset}`); + }, + + appendViaStream(fd) { + console.log(`[filesystem] APPEND STREAM ${fd}`); + }, + + advise(fd, offset, length, advice) { + console.log(`[filesystem] ADVISE`, fd, offset, length, advice); + }, + + syncData(fd) { + console.log(`[filesystem] SYNC DATA ${fd}`); + }, + + getFlags(fd) { + console.log(`[filesystem] FLAGS FOR ${fd}`); + }, + + getType(fd) { + console.log(`[filesystem] GET TYPE ${fd}`); + }, + + setFlags(fd, flags) { + console.log(`[filesystem] SET FLAGS ${fd} ${JSON.stringify(flags)}`); + }, + + setSize(fd, size) { + console.log(`[filesystem] SET SIZE`, fd, size); + }, + + setTimes(fd, dataAccessTimestamp, dataModificationTimestamp) { + console.log(`[filesystem] SET TIMES`, fd, dataAccessTimestamp, dataModificationTimestamp); + }, + + read(fd, length, offset) { + console.log(`[filesystem] READ`, fd, length, offset); + }, + + write(fd, buffer, offset) { + console.log(`[filesystem] WRITE`, fd, buffer, offset); + }, + + readDirectory(fd) { + console.log(`[filesystem] READ DIR`, fd); + }, + + sync(fd) { + console.log(`[filesystem] SYNC`, fd); + }, + + createDirectoryAt(fd, path) { + console.log(`[filesystem] CREATE DIRECTORY`, fd, path); + }, + + stat(fd) { + console.log(`[filesystem] STAT`, fd); + }, + + statAt(fd, pathFlags, path) { + console.log(`[filesystem] STAT`, fd, pathFlags, path); + }, + + setTimesAt(fd) { + console.log(`[filesystem] SET TIMES AT`, fd); + }, + + linkAt(fd) { + console.log(`[filesystem] LINK AT`, fd); + }, + + openAt(fd) { + console.log(`[filesystem] OPEN AT ${fd}`); + }, + + readlinkAt(fd) { + console.log(`[filesystem] READLINK AT`, fd); + }, + + removeDirectoryAt(fd) { + console.log(`[filesystem] REMOVE DIR AT`, fd); + }, + + renameAt(fd) { + console.log(`[filesystem] RENAME AT`, fd); + }, + + symlinkAt(fd) { + console.log(`[filesystem] SYMLINK AT`, fd); + }, + + unlinkFileAt(fd) { + console.log(`[filesystem] UNLINK FILE AT`, fd); + }, + + changeFilePermissionsAt(fd) { + console.log(`[filesystem] CHANGE FILE PERMISSIONS AT`, fd); + }, -export function changeFilePermissionsAt(fd) { - console.log(`[filesystem] CHANGE FILE PERMISSIONS AT`, fd); -} + changeDirectoryPermissionsAt(fd) { + console.log(`[filesystem] CHANGE DIR PERMISSIONS AT`, fd); + }, -export function changeDirectoryPermissionsAt(fd) { - console.log(`[filesystem] CHANGE DIR PERMISSIONS AT`, fd); -} + lockShared(fd) { + console.log(`[filesystem] LOCK SHARED`, fd); + }, -export function lockShared(fd) { - console.log(`[filesystem] LOCK SHARED`, fd); -} + lockExclusive(fd) { + console.log(`[filesystem] LOCK EXCLUSIVE`, fd); + }, -export function lockExclusive(fd) { - console.log(`[filesystem] LOCK EXCLUSIVE`, fd); -} + tryLockShared(fd) { + console.log(`[filesystem] TRY LOCK SHARED`, fd); + }, -export function tryLockShared(fd) { - console.log(`[filesystem] TRY LOCK SHARED`, fd); -} + tryLockExclusive(fd) { + console.log(`[filesystem] TRY LOCK EXCLUSIVE`, fd); + }, -export function tryLockExclusive(fd) { - console.log(`[filesystem] TRY LOCK EXCLUSIVE`, fd); -} + unlock(fd) { + console.log(`[filesystem] UNLOCK`, fd); + }, -export function unlock(fd) { - console.log(`[filesystem] UNLOCK`, fd); -} + dropDescriptor(fd) { + console.log(`[filesystem] DROP DESCRIPTOR`, fd); + }, -export function dropDescriptor(fd) { - console.log(`[filesystem] DROP DESCRIPTOR`, fd); -} + readDirectoryEntry(stream) { + console.log(`[filesystem] READ DIRECTRY ENTRY`, stream); + }, -export function readDirectoryEntry(stream) { - console.log(`[filesystem] READ DIRECTRY ENTRY`, stream); -} - -export function dropDirectoryEntryStream(stream) { - console.log(`[filesystem] DROP DIRECTORY ENTRY`, stream); -} + dropDirectoryEntryStream(stream) { + console.log(`[filesystem] DROP DIRECTORY ENTRY`, stream); + } +}; + +export { filesystem as filesystemFilesystem } diff --git a/packages/preview2-shim/lib/browser/http.js b/packages/preview2-shim/lib/browser/http.js index 031eba30d..82aea80f6 100644 --- a/packages/preview2-shim/lib/browser/http.js +++ b/packages/preview2-shim/lib/browser/http.js @@ -33,3 +33,123 @@ export function send(req) { throw new UnexpectedError(err.message); } } + +export const httpIncomingHandler = { + handle () { + + } +}; + +export const httpOutgoingHandler = { + handle () { + + } +}; + +export const httpTypes = { + dropFields(_fields) { + console.log("[types] Drop fields"); + }, + newFields(_entries) { + console.log("[types] New fields"); + }, + fieldsGet(_fields, _name) { + console.log("[types] Fields get"); + }, + fieldsSet(_fields, _name, _value) { + console.log("[types] Fields set"); + }, + fieldsDelete(_fields, _name) { + console.log("[types] Fields delete"); + }, + fieldsAppend(_fields, _name, _value) { + console.log("[types] Fields append"); + }, + fieldsEntries(_fields) { + console.log("[types] Fields entries"); + }, + fieldsClone(_fields) { + console.log("[types] Fields clone"); + }, + finishIncomingStream(s) { + console.log(`[types] Finish incoming stream ${s}`); + }, + finishOutgoingStream(s, _trailers) { + console.log(`[types] Finish outgoing stream ${s}`); + }, + dropIncomingRequest(_req) { + console.log("[types] Drop incoming request"); + }, + dropOutgoingRequest(_req) { + console.log("[types] Drop outgoing request"); + }, + incomingRequestMethod(_req) { + console.log("[types] Incoming request method"); + }, + incomingRequestPath(_req) { + console.log("[types] Incoming request path"); + }, + incomingRequestQuery(_req) { + console.log("[types] Incoming request query"); + }, + incomingRequestScheme(_req) { + console.log("[types] Incoming request scheme"); + }, + incomingRequestAuthority(_req) { + console.log("[types] Incoming request authority"); + }, + incomingRequestHeaders(_req) { + console.log("[types] Incoming request headers"); + }, + incomingRequestConsume(_req) { + console.log("[types] Incoming request consume"); + }, + newOutgoingRequest(_method, _path, _query, _scheme, _authority, _headers) { + console.log("[types] New outgoing request"); + }, + outgoingRequestWrite(_req) { + console.log("[types] Outgoing request write"); + }, + dropResponseOutparam(_res) { + console.log("[types] Drop response outparam"); + }, + setResponseOutparam(_response) { + console.log("[types] Drop fields"); + }, + dropIncomingResponse(_res) { + console.log("[types] Drop incoming response"); + }, + dropOutgoingResponse(_res) { + console.log("[types] Drop outgoing response"); + }, + incomingResponseStatus(_res) { + console.log("[types] Incoming response status"); + }, + incomingResponseHeaders(_res) { + console.log("[types] Incoming response headers"); + }, + incomingResponseConsume(_res) { + console.log("[types] Incoming response consume"); + }, + newOutgoingResponse(_statusCode, _headers) { + console.log("[types] New outgoing response"); + }, + outgoingResponseWrite(_res) { + console.log("[types] Outgoing response write"); + }, + dropFutureIncomingResponse(_f) { + console.log("[types] Drop future incoming response"); + }, + futureIncomingResponseGet(_f) { + console.log("[types] Future incoming response get"); + }, + listenToFutureIncomingResponse(_f) { + console.log("[types] Listen to future incoming response"); + } +}; + +export { + httpIncomingHandler as incomingHandler, + httpOutgoingHandler as outgoingHandler, + httpTypes as types +} diff --git a/packages/preview2-shim/lib/browser/index.js b/packages/preview2-shim/lib/browser/index.js index c7bc2cd6f..0935e25c6 100644 --- a/packages/preview2-shim/lib/browser/index.js +++ b/packages/preview2-shim/lib/browser/index.js @@ -1,45 +1,23 @@ -import * as console from "./console.js"; -import * as defaultOutgoingHttp from "./default-outgoing-HTTP.js"; -import * as environment from "./environment.js"; -import * as exit from "./exit.js"; +import * as clocks from "./clocks.js"; import * as filesystem from "./filesystem.js"; -import * as instanceNetwork from "./instance-network.js"; -import * as ipNameLookup from "./ip-name-lookup.js"; -import * as monotonicClock from "./monotonic-clock.js"; -import * as network from "./network.js"; +import * as http from "./http.js"; +import * as io from "./io.js"; +import * as logging from "./logging.js"; import * as poll from "./poll.js"; -import * as preopens from "./preopens.js"; import * as random from "./random.js"; -import * as streams from "./streams.js"; -import * as tcpCreateSocket from "./tcp-create-socket.js"; -import * as tcp from "./tcp.js"; -import * as timezone from "./timezone.js"; -import * as types from "./types.js"; -import * as udpCreateSocket from "./udp-create-socket.js"; -import * as udp from "./udp.js"; -import * as wallClock from "./wall-clock.js"; +import * as sockets from "./sockets.js"; +import * as cliBase from "./cli-base.js"; export const importObject = { - 'console': console, - 'default-outgoing-HTTP': defaultOutgoingHttp, - 'environment': environment, - 'exit': exit, - 'filesystem': filesystem, - 'instance-network': instanceNetwork, - 'ip-name-lookup': ipNameLookup, - 'monotonic-clock': monotonicClock, - 'network': network, - 'poll': poll, - 'preopens': preopens, - 'random': random, - 'streams': streams, - 'tcp-create-socket': tcpCreateSocket, - 'tcp': tcp, - 'timezone': timezone, - 'types': types, - 'udp-create-socket': udpCreateSocket, - 'udp': udp, - 'wall-clock': wallClock + clocks, + filesystem, + http, + io, + logging, + poll, + random, + sockets, + cliBase, }; export { WasiHttp } from "../http/wasi-http.js"; diff --git a/packages/preview2-shim/lib/browser/instance-network.js b/packages/preview2-shim/lib/browser/instance-network.js deleted file mode 100644 index 8c108b824..000000000 --- a/packages/preview2-shim/lib/browser/instance-network.js +++ /dev/null @@ -1,3 +0,0 @@ -export function instanceNetwork () { - -} diff --git a/packages/preview2-shim/lib/browser/io.js b/packages/preview2-shim/lib/browser/io.js index 68a35ffd6..ec4cfbaea 100644 --- a/packages/preview2-shim/lib/browser/io.js +++ b/packages/preview2-shim/lib/browser/io.js @@ -1,22 +1,64 @@ -export function read(s, len) { - console.log(`[io] Read ${s} ${len}`); -} - -export function write(s, buf) { - switch (s) { - case 0: - throw new Error(`TODO: write stdin`); - case 1: { - const decoder = new TextDecoder(); - console.log(decoder.decode(buf)); - return BigInt(buf.byteLength); - } - case 2: { - const decoder = new TextDecoder(); - console.error(decoder.decode(buf)); - return BigInt(buf.byteLength); +export const ioStreams = { + read(s, len) { + console.log(`[streams] Read ${s} ${len}`); + }, + blockingRead(s, len) { + console.log(`[streams] Blocking read ${s} ${len}`); + }, + skip(s, _len) { + console.log(`[streams] Skip ${s}`); + }, + blockingSkip(s, _len) { + console.log(`[streams] Blocking skip ${s}`); + }, + subscribeToInputStream(s) { + console.log(`[streams] Subscribe to input stream ${s}`); + }, + dropInputStream(s) { + console.log(`[streams] Drop input stream ${s}`); + }, + write(s, buf) { + switch (s) { + case 0: + throw new Error(`TODO: write stdin`); + case 1: { + const decoder = new TextDecoder(); + console.log(decoder.decode(buf)); + return BigInt(buf.byteLength); + } + case 2: { + const decoder = new TextDecoder(); + console.error(decoder.decode(buf)); + return BigInt(buf.byteLength); + } + default: + throw new Error(`TODO: write ${s}`); } - default: - throw new Error(`TODO: write ${s}`); + }, + blockingWrite(s, _buf) { + console.log(`[streams] Blocking write ${s}`); + }, + writeZeroes(s, _len) { + console.log(`[streams] Write zeroes ${s}`); + }, + blockingWriteZeroes(s, _len) { + console.log(`[streams] Blocking write zeroes ${s}`); + }, + splice(s, _src, _len) { + console.log(`[streams] Splice ${s}`); + }, + blockingSplice(s, _src, _len) { + console.log(`[streams] Blocking splice ${s}`); + }, + forward(s, _src) { + console.log(`[streams] Forward ${s}`); + }, + subscribeToOutputStream(s) { + console.log(`[streams] Subscribe to output stream ${s}`); + }, + dropOutputStream(s) { + console.log(`[streams] Drop output stream ${s}`); } -} +}; + +export { ioStreams as streams } diff --git a/packages/preview2-shim/lib/browser/ip-name-lookup.js b/packages/preview2-shim/lib/browser/ip-name-lookup.js deleted file mode 100644 index e1a449704..000000000 --- a/packages/preview2-shim/lib/browser/ip-name-lookup.js +++ /dev/null @@ -1,23 +0,0 @@ -export function dropResolveAddressStream () { - -} - -export function subscribe () { - -} - -export function resolveAddresses () { - -} - -export function resolveNextAddress () { - -} - -export function nonBlocking () { - -} - -export function setNonBlocking () { - -} diff --git a/packages/preview2-shim/lib/browser/logging.js b/packages/preview2-shim/lib/browser/logging.js new file mode 100644 index 000000000..e41a2d772 --- /dev/null +++ b/packages/preview2-shim/lib/browser/logging.js @@ -0,0 +1,16 @@ +const levels = ["trace", "debug", "info", "warn", "error"]; + +let logLevel = levels.indexOf("warn"); + +export const loggingHandler = { + log(level, context, msg) { + if (logLevel > levels.indexOf(level)) return; + console[level](`(${context}) ${msg}\n`); + } +}; + +export function setLevel(level) { + logLevel = levels.indexOf(level); +} + +export { loggingHandler as handler } diff --git a/packages/preview2-shim/lib/browser/monotonic-clock.js b/packages/preview2-shim/lib/browser/monotonic-clock.js deleted file mode 100644 index 6e31ffe1c..000000000 --- a/packages/preview2-shim/lib/browser/monotonic-clock.js +++ /dev/null @@ -1,14 +0,0 @@ - -export function resolution(clock) { - console.log(`[monotonic-clock] Monotonic clock resolution ${clock}`); -} - -function _hrtimeBigint () { - return BigInt(Math.floor(performance.now() * 1e9)); -} - -let _hrStart = _hrtimeBigint(); - -export function now () { - return _hrtimeBigint() - _hrStart; -} diff --git a/packages/preview2-shim/lib/browser/network.js b/packages/preview2-shim/lib/browser/network.js deleted file mode 100644 index 0f1f0ea0d..000000000 --- a/packages/preview2-shim/lib/browser/network.js +++ /dev/null @@ -1,3 +0,0 @@ -export function dropNetwork () { - -} diff --git a/packages/preview2-shim/lib/browser/poll.js b/packages/preview2-shim/lib/browser/poll.js index 60dc3c05c..e66f24beb 100644 --- a/packages/preview2-shim/lib/browser/poll.js +++ b/packages/preview2-shim/lib/browser/poll.js @@ -1,7 +1,11 @@ -export function dropPollable(p) { - console.log(`[poll] Drop pollable ${p}`); -} +export const pollPoll = { + dropPollable (pollable) { + console.log(`[poll] Drop (${pollable})`); + }, + pollOneoff (input) { + console.log(`[poll] Oneoff (${input})`); + return []; + } +}; -export function pollOneoff(f) { - console.log(`[poll] Poll oneoff ${f}`); -} +export { pollPoll as poll } diff --git a/packages/preview2-shim/lib/browser/preopens.js b/packages/preview2-shim/lib/browser/preopens.js deleted file mode 100644 index dd98e8edf..000000000 --- a/packages/preview2-shim/lib/browser/preopens.js +++ /dev/null @@ -1,11 +0,0 @@ -export function getStdio () { - return { - stdin: 0, - stdout: 1, - stderr: 2, - }; -} - -export function getDirectories () { - return []; -} diff --git a/packages/preview2-shim/lib/browser/random.js b/packages/preview2-shim/lib/browser/random.js index 7d3d0f3b9..ab45e900b 100644 --- a/packages/preview2-shim/lib/browser/random.js +++ b/packages/preview2-shim/lib/browser/random.js @@ -1,32 +1,39 @@ const MAX_BYTES = 65536; -export function getRandomBytes(len) { - const bytes = new Uint8Array(Number(len)); - - if (len > MAX_BYTES) { - // this is the max bytes crypto.getRandomValues - // can do at once see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues - for (var generated = 0; generated < len; generated += MAX_BYTES) { - // buffer.slice automatically checks if the end is past the end of - // the buffer so we don't have to here - crypto.getRandomValues(bytes.slice(generated, generated + MAX_BYTES)); - } - } else { - crypto.getRandomValues(bytes); - } - - return bytes; -} +let insecureRandomValue1, insecureRandomValue2; -export function getRandomU64 () { +function getRandomU64 () { return crypto.getRandomValues(new BigUint64Array(1))[0]; } -let insecureRandomValue1, insecureRandomValue2; -export function insecureRandom () { - if (insecureRandomValue1 === undefined) { - insecureRandomValue1 = getRandomU64(); - insecureRandomValue2 = getRandomU64(); +export const randomRandom = { + getRandomBytes(len) { + const bytes = new Uint8Array(Number(len)); + + if (len > MAX_BYTES) { + // this is the max bytes crypto.getRandomValues + // can do at once see https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues + for (var generated = 0; generated < len; generated += MAX_BYTES) { + // buffer.slice automatically checks if the end is past the end of + // the buffer so we don't have to here + crypto.getRandomValues(bytes.slice(generated, generated + MAX_BYTES)); + } + } else { + crypto.getRandomValues(bytes); + } + + return bytes; + }, + + getRandomU64: getRandomU64, + + insecureRandom () { + if (insecureRandomValue1 === undefined) { + insecureRandomValue1 = getRandomU64(); + insecureRandomValue2 = getRandomU64(); + } + return [insecureRandomValue1, insecureRandomValue2]; } - return [insecureRandomValue1, insecureRandomValue2]; -} +}; + +export { randomRandom as random } diff --git a/packages/preview2-shim/lib/browser/sockets.js b/packages/preview2-shim/lib/browser/sockets.js new file mode 100644 index 000000000..210e6ccb3 --- /dev/null +++ b/packages/preview2-shim/lib/browser/sockets.js @@ -0,0 +1,203 @@ +export const socketsInstanceNetwork = { + instanceNetwork () { + console.log(`[sockets] instance network`); + } +}; + +export const socketsIpNameLookup = { + dropResolveAddressStream () { + + }, + subscribe () { + + }, + resolveAddresses () { + + }, + resolveNextAddress () { + + }, + nonBlocking () { + + }, + setNonBlocking () { + + }, +}; + +export const socketsNetwork = { + dropNetwork () { + + } +}; + +export const socketsTcpCreateSocket = { + createTcpSocket () { + + } +}; + +export const socketsTcp = { + subscribe () { + + }, + dropTcpSocket() { + + }, + bind() { + + }, + connect() { + + }, + listen() { + + }, + accept() { + + }, + localAddress() { + + }, + remoteAddress() { + + }, + addressFamily() { + + }, + ipv6Only() { + + }, + setIpv6Only() { + + }, + setListenBacklogSize() { + + }, + keepAlive() { + + }, + setKeepAlive() { + + }, + noDelay() { + + }, + setNoDelay() { + + }, + unicastHopLimit() { + + }, + setUnicastHopLimit() { + + }, + receiveBufferSize() { + + }, + setReceiveBufferSize() { + + }, + sendBufferSize() { + + }, + setSendBufferSize() { + + }, + nonBlocking() { + + }, + setNonBlocking() { + + }, + shutdown() { + + } +}; + +export const socketsUdp = { + subscribe () { + + }, + + dropUdpSocket () { + + }, + + bind () { + + }, + + connect () { + + }, + + receive () { + + }, + + send () { + + }, + + localAddress () { + + }, + + remoteAddress () { + + }, + + addressFamily () { + + }, + + ipv6Only () { + + }, + + setIpv6Only () { + + }, + + unicastHopLimit () { + + }, + + setUnicastHopLimit () { + + }, + + receiveBufferSize () { + + }, + + setReceiveBufferSize () { + + }, + + sendBufferSize () { + + }, + + setSendBufferSize () { + + }, + + nonBlocking () { + + }, + + setNonBlocking () { + + } +}; + +export { + socketsInstanceNetwork as instanceNetwork, + socketsIpNameLookup as ipNameLookup, + socketsNetwork as network, + socketsTcpCreateSocket as tcpCreateSocket, + socketsTcp as tcp, + socketsUdp as udp +} diff --git a/packages/preview2-shim/lib/browser/stderr.js b/packages/preview2-shim/lib/browser/stderr.js deleted file mode 100644 index 8a603d5d8..000000000 --- a/packages/preview2-shim/lib/browser/stderr.js +++ /dev/null @@ -1,4 +0,0 @@ -// TODO: remove -export function print(message) { - console.error(message); -} diff --git a/packages/preview2-shim/lib/browser/streams.js b/packages/preview2-shim/lib/browser/streams.js deleted file mode 100644 index 24d2cb818..000000000 --- a/packages/preview2-shim/lib/browser/streams.js +++ /dev/null @@ -1,60 +0,0 @@ -export function read(s, _len) { - console.log(`[streams] Read ${s}`); -} -export function blockingRead(s, _len) { - console.log(`[streams] Blocking read ${s}`); -} -export function skip(s, _len) { - console.log(`[streams] Skip ${s}`); -} -export function blockingSkip(s, _len) { - console.log(`[streams] Blocking skip ${s}`); -} -export function subscribeToInputStream(s) { - console.log(`[streams] Subscribe to input stream ${s}`); -} -export function dropInputStream(s) { - console.log(`[streams] Drop input stream ${s}`); -} -export function write(s, buf) { - switch (s) { - case 0: - throw new Error(`TODO: write stdin`); - case 1: { - const decoder = new TextDecoder(); - console.log(decoder.decode(buf)); - return BigInt(buf.byteLength); - } - case 2: { - const decoder = new TextDecoder(); - console.error(decoder.decode(buf)); - return BigInt(buf.byteLength); - } - default: - throw new Error(`TODO: write ${s}`); - } -} -export function blockingWrite(s, _buf) { - console.log(`[streams] Blocking write ${s}`); -} -export function writeZeroes(s, _len) { - console.log(`[streams] Write zeroes ${s}`); -} -export function blockingWriteZeroes(s, _len) { - console.log(`[streams] Blocking write zeroes ${s}`); -} -export function splice(s, _src, _len) { - console.log(`[streams] Splice ${s}`); -} -export function blockingSplice(s, _src, _len) { - console.log(`[streams] Blocking splice ${s}`); -} -export function forward(s, _src) { - console.log(`[streams] Forward ${s}`); -} -export function subscribeToOutputStream(s) { - console.log(`[streams] Subscribe to output stream ${s}`); -} -export function dropOutputStream(s) { - console.log(`[streams] Drop output stream ${s}`); -} diff --git a/packages/preview2-shim/lib/browser/tcp-create-socket.js b/packages/preview2-shim/lib/browser/tcp-create-socket.js deleted file mode 100644 index 0cf6401dd..000000000 --- a/packages/preview2-shim/lib/browser/tcp-create-socket.js +++ /dev/null @@ -1,3 +0,0 @@ -export function createTcpSocket () { - -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/browser/tcp.js b/packages/preview2-shim/lib/browser/tcp.js deleted file mode 100644 index 45dbf9d98..000000000 --- a/packages/preview2-shim/lib/browser/tcp.js +++ /dev/null @@ -1,75 +0,0 @@ -export function subscribe () { - -} -export function dropTcpSocket() { - -} -export function bind() { - -} -export function connect() { - -} -export function listen() { - -} -export function accept() { - -} -export function localAddress() { - -} -export function remoteAddress() { - -} -export function addressFamily() { - -} -export function ipv6Only() { - -} -export function setIpv6Only() { - -} -export function setListenBacklogSize() { - -} -export function keepAlive() { - -} -export function setKeepAlive() { - -} -export function noDelay() { - -} -export function setNoDelay() { - -} -export function unicastHopLimit() { - -} -export function setUnicastHopLimit() { - -} -export function receiveBufferSize() { - -} -export function setReceiveBufferSize() { - -} -export function sendBufferSize() { - -} -export function setSendBufferSize() { - -} -export function nonBlocking() { - -} -export function setNonBlocking() { - -} -export function shutdown() { - -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/browser/timezone.js b/packages/preview2-shim/lib/browser/timezone.js deleted file mode 100644 index 7568396f0..000000000 --- a/packages/preview2-shim/lib/browser/timezone.js +++ /dev/null @@ -1,12 +0,0 @@ -export function display (timezone, when) { - console.log(`[timezone] DISPLAY ${timezone} ${when}`); -} - -export function utcOffset (timezone, when) { - console.log(`[timezone] UTC OFFSET ${timezone} ${when}`); - return 0; -} - -export function dropTimezone (timezone) { - console.log(`[timezone] DROP ${timezone}`); -} diff --git a/packages/preview2-shim/lib/browser/types.js b/packages/preview2-shim/lib/browser/types.js deleted file mode 100644 index 8a66ecf2e..000000000 --- a/packages/preview2-shim/lib/browser/types.js +++ /dev/null @@ -1,99 +0,0 @@ -export function dropFields(_fields) { - console.log("[types] Drop fields"); -} -export function newFields(_entries) { - console.log("[types] New fields"); -} -export function fieldsGet(_fields, _name) { - console.log("[types] Fields get"); -} -export function fieldsSet(_fields, _name, _value) { - console.log("[types] Fields set"); -} -export function fieldsDelete(_fields, _name) { - console.log("[types] Fields delete"); -} -export function fieldsAppend(_fields, _name, _value) { - console.log("[types] Fields append"); -} -export function fieldsEntries(_fields) { - console.log("[types] Fields entries"); -} -export function fieldsClone(_fields) { - console.log("[types] Fields clone"); -} -export function finishIncomingStream(s) { - console.log(`[types] Finish incoming stream ${s}`); -} -export function finishOutgoingStream(s, _trailers) { - console.log(`[types] Finish outgoing stream ${s}`); -} -export function dropIncomingRequest(_req) { - console.log("[types] Drop incoming request"); -} -export function dropOutgoingRequest(_req) { - console.log("[types] Drop outgoing request"); -} -export function incomingRequestMethod(_req) { - console.log("[types] Incoming request method"); -} -export function incomingRequestPath(_req) { - console.log("[types] Incoming request path"); -} -export function incomingRequestQuery(_req) { - console.log("[types] Incoming request query"); -} -export function incomingRequestScheme(_req) { - console.log("[types] Incoming request scheme"); -} -export function incomingRequestAuthority(_req) { - console.log("[types] Incoming request authority"); -} -export function incomingRequestHeaders(_req) { - console.log("[types] Incoming request headers"); -} -export function incomingRequestConsume(_req) { - console.log("[types] Incoming request consume"); -} -export function newOutgoingRequest(_method, _path, _query, _scheme, _authority, _headers) { - console.log("[types] New outgoing request"); -} -export function outgoingRequestWrite(_req) { - console.log("[types] Outgoing request write"); -} -export function dropResponseOutparam(_res) { - console.log("[types] Drop response outparam"); -} -export function setResponseOutparam(_res) { - console.log("[types] Drop fields"); -} -export function dropIncomingResponse(_res) { - console.log("[types] Drop incoming response"); -} -export function dropOutgoingResponse(_res) { - console.log("[types] Drop outgoing response"); -} -export function incomingResponseStatus(_res) { - console.log("[types] Incoming response status"); -} -export function incomingResponseHeaders(_res) { - console.log("[types] Incoming response headers"); -} -export function incomingResponseConsume(_res) { - console.log("[types] Incoming response consume"); -} -export function newOutgoingResponse(_statusCode, _headers) { - console.log("[types] New outgoing response"); -} -export function outgoingResponseWrite(_res) { - console.log("[types] Outgoing response write"); -} -export function dropFutureIncomingResponse(_f) { - console.log("[types] Drop future incoming response"); -} -export function futureIncomingResponseGet(_f) { - console.log("[types] Future incoming response get"); -} -export function listenToFutureIncomingResponse(_f) { - console.log("[types] Listen to future incoming response"); -} diff --git a/packages/preview2-shim/lib/browser/udp-create-socket.js b/packages/preview2-shim/lib/browser/udp-create-socket.js deleted file mode 100644 index b0a86b496..000000000 --- a/packages/preview2-shim/lib/browser/udp-create-socket.js +++ /dev/null @@ -1,3 +0,0 @@ -export function createUdpSocket () { - -} diff --git a/packages/preview2-shim/lib/browser/udp.js b/packages/preview2-shim/lib/browser/udp.js deleted file mode 100644 index 9f47bc4d4..000000000 --- a/packages/preview2-shim/lib/browser/udp.js +++ /dev/null @@ -1,75 +0,0 @@ -export function subscribe () { - -} - -export function dropUdpSocket () { - -} - -export function bind () { - -} - -export function connect () { - -} - -export function receive () { - -} - -export function send () { - -} - -export function localAddress () { - -} - -export function remoteAddress () { - -} - -export function addressFamily () { - -} - -export function ipv6Only () { - -} - -export function setIpv6Only () { - -} - -export function unicastHopLimit () { - -} - -export function setUnicastHopLimit () { - -} - -export function receiveBufferSize () { - -} - -export function setReceiveBufferSize () { - -} - -export function sendBufferSize () { - -} - -export function setSendBufferSize () { - -} - -export function nonBlocking () { - -} - -export function setNonBlocking () { - -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/browser/wall-clock.js b/packages/preview2-shim/lib/browser/wall-clock.js deleted file mode 100644 index 0ff3988f2..000000000 --- a/packages/preview2-shim/lib/browser/wall-clock.js +++ /dev/null @@ -1,9 +0,0 @@ -export function now() { - const seconds = BigInt(Math.floor(Date.now() / 1e3)); - const nanoseconds = (Date.now() % 1e3) * 1e6; - return { seconds, nanoseconds }; -} - -export function resolution() { - console.log(`[wall-clock] Wall clock resolution`); -} diff --git a/packages/preview2-shim/lib/nodejs/preopens.js b/packages/preview2-shim/lib/nodejs/cli-base.js similarity index 66% rename from packages/preview2-shim/lib/nodejs/preopens.js rename to packages/preview2-shim/lib/nodejs/cli-base.js index 737b9beab..348803770 100644 --- a/packages/preview2-shim/lib/nodejs/preopens.js +++ b/packages/preview2-shim/lib/nodejs/cli-base.js @@ -56,14 +56,53 @@ export function _setPreopens (preopens) { } } -export function getStdio () { - return { - stdin: 0, - stdout: 1, - stderr: 2, - }; +let _env; +export function _setEnv (envObj) { + _env = Object.entries(envObj); } -export function getDirectories () { - return directories; +export const cliBaseEnvironment = { + getEnvironment () { + if (!_env) _setEnv(process.env); + return _env; + } +}; + +export const cliBaseExit = { + exit (status) { + process.exit(status.tag === 'err' ? 1 : 0); + } +}; + +export const cliBasePreopens = { + getDirectories () { + return directories; + } +} + +export const cliBaseStdin = { + getStdin () { + return 0; + } +}; + +export const cliBaseStdout = { + getStdout () { + return 1; + } +}; + +export const cliBaseStderr = { + getStderr () { + return 2; + } +}; + +export { + cliBaseEnvironment as environment, + cliBaseExit as exit, + cliBasePreopens as preopens, + cliBaseStdin as stdin, + cliBaseStdout as stdout, + cliBaseStderr as stderr } diff --git a/packages/preview2-shim/lib/nodejs/clocks.js b/packages/preview2-shim/lib/nodejs/clocks.js new file mode 100644 index 000000000..90e50932a --- /dev/null +++ b/packages/preview2-shim/lib/nodejs/clocks.js @@ -0,0 +1,50 @@ +import { hrtime } from "node:process"; + +let _hrStart = hrtime.bigint(); + +export const clocksMonotonicClock = { + resolution () { + return 1n; + }, + + now () { + return hrtime.bigint() - _hrStart; + }, + + subscribe (_when, _absolute) { + console.log(`[monotonic-clock] Subscribe`); + } +}; + +export const clocksTimezone = { + display (timezone, when) { + console.log(`[timezone] DISPLAY ${timezone} ${when}`); + }, + + utcOffset (timezone, when) { + console.log(`[timezone] UTC OFFSET ${timezone} ${when}`); + return 0; + }, + + dropTimezone (timezone) { + console.log(`[timezone] DROP ${timezone}`); + } +}; + +export const clocksWallClock = { + now() { + const seconds = BigInt(Math.floor(Date.now() / 1e3)); + const nanoseconds = (Date.now() % 1e3) * 1e6; + return { seconds, nanoseconds }; + }, + + resolution() { + console.log(`[wall-clock] Wall clock resolution`); + } +}; + +export { + clocksMonotonicClock as monotonicClock, + clocksTimezone as timezone, + clocksWallClock as wallClock +} diff --git a/packages/preview2-shim/lib/nodejs/console.js b/packages/preview2-shim/lib/nodejs/console.js deleted file mode 100644 index b19b831f8..000000000 --- a/packages/preview2-shim/lib/nodejs/console.js +++ /dev/null @@ -1,12 +0,0 @@ -const levels = ["trace", "debug", "info", "warn", "error"]; - -let logLevel = levels.indexOf("warn"); - -export function setLevel(level) { - logLevel = levels.indexOf(level); -} - -export function log(level, context, msg) { - if (logLevel > levels.indexOf(level)) return; - process.stdout.write(`${level}: (${context}) ${msg}\n`); -} diff --git a/packages/preview2-shim/lib/nodejs/default-outgoing-HTTP.js b/packages/preview2-shim/lib/nodejs/default-outgoing-HTTP.js deleted file mode 100644 index bb24fa000..000000000 --- a/packages/preview2-shim/lib/nodejs/default-outgoing-HTTP.js +++ /dev/null @@ -1,3 +0,0 @@ -export function handle(_request, _options) { - console.log("[default-outgoing-HTTP] Handle"); -} diff --git a/packages/preview2-shim/lib/nodejs/environment.js b/packages/preview2-shim/lib/nodejs/environment.js deleted file mode 100644 index 28e5b8cda..000000000 --- a/packages/preview2-shim/lib/nodejs/environment.js +++ /dev/null @@ -1,19 +0,0 @@ -import process from 'node:process'; - -let _env; -export function _setEnv (envObj) { - _env = Object.entries(envObj); -} - -export function getEnvironment () { - if (!_env) _setEnv(process.env); - return _env; -} - -export function preopens () { - return []; -} - -export function getArguments () { - return []; -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/nodejs/exit.js b/packages/preview2-shim/lib/nodejs/exit.js deleted file mode 100644 index a65f5f5a8..000000000 --- a/packages/preview2-shim/lib/nodejs/exit.js +++ /dev/null @@ -1,3 +0,0 @@ -export function exit(status) { - process.exit(status.tag === 'err' ? 1 : 0); -} diff --git a/packages/preview2-shim/lib/nodejs/filesystem.js b/packages/preview2-shim/lib/nodejs/filesystem.js index 9fa6c6c96..ad876365c 100644 --- a/packages/preview2-shim/lib/nodejs/filesystem.js +++ b/packages/preview2-shim/lib/nodejs/filesystem.js @@ -1,81 +1,8 @@ import { openSync, constants, statSync, lstatSync, fstatSync, closeSync, readdirSync } from 'node:fs'; -import { _descriptors, _addOpenedDescriptor, _removeOpenedDescriptor, _getDescriptorType, _setSubdescriptorType, _setDescriptorType, _getFullPath } from './preopens.js'; -import { _createFileStream } from './streams.js'; - -export function readViaStream(fd, offset) { - return _createFileStream(fd, offset); -} - -export function writeViaStream(fd, offset) { - console.log(`[filesystem] WRITE STREAM ${fd} ${offset}`); -} - -export function appendViaStream(fd) { - console.log(`[filesystem] APPEND STREAM ${fd}`); -} - -export function advise(fd, offset, length, advice) { - console.log(`[filesystem] ADVISE`, fd, offset, length, advice); -} - -export function syncData(fd) { - console.log(`[filesystem] SYNC DATA ${fd}`); -} - -export function getFlags(fd) { - console.log(`[filesystem] FLAGS FOR ${fd}`); -} - -export function getType(fd) { - let type = _getDescriptorType(fd); - if (type === null) { - stat(fd); - type = _getDescriptorType(fd); - } - return type; -} - -export function setFlags(fd, flags) { - console.log(`[filesystem] SET FLAGS ${fd} ${JSON.stringify(flags)}`); -} - -export function setSize(fd, size) { - console.log(`[filesystem] SET SIZE`, fd, size); -} - -export function setTimes(fd, dataAccessTimestamp, dataModificationTimestamp) { - console.log(`[filesystem] SET TIMES`, fd, dataAccessTimestamp, dataModificationTimestamp); -} - -export function read(fd, length, offset) { - console.log(`[filesystem] READ`, fd, length, offset); -} - -export function write(fd, buffer, offset) { - console.log(`[filesystem] WRITE`, fd, buffer, offset); -} +import { _descriptors, _addOpenedDescriptor, _removeOpenedDescriptor, _getDescriptorType, _setSubdescriptorType, _setDescriptorType, _getFullPath } from './cli-base.js'; +import { _createFileStream } from './io.js'; let _dirStreams = []; -export function readDirectory(fd) { - const fullPath = _getFullPath(fd); - let dirs; - try { - dirs = readdirSync(fullPath, { withFileTypes: true }); - } - catch (e) { - _convertFsError(e); - } - _dirStreams.push({ fd, dirs, cursor: 0 }); - return _dirStreams.length - 1; -} - -export function sync(fd) { - console.log(`[filesystem] SYNC`, fd); -} - -export function createDirectoryAt(fd, path) { - console.log(`[filesystem] CREATE DIRECTORY`, fd, path); -} const nsMagnitude = 1_000_000_000_000n; function nsToDateTime (ns) { @@ -84,7 +11,7 @@ function nsToDateTime (ns) { return { seconds, nanoseconds }; } -export function _convertFsError (e) { +function _convertFsError (e) { switch (e.code) { case 'EACCES': throw 'access'; case 'EAGAIN': @@ -146,163 +73,241 @@ function _lookupType (obj) { return 'unknown'; } -export function stat(fd) { - let stats; - try { - stats = fstatSync(fd, { bigint: true }); +export const filesystem = { + readViaStream(fd, offset) { + return _createFileStream(fd, offset); + }, + + writeViaStream(fd, offset) { + console.log(`[filesystem] WRITE STREAM ${fd} ${offset}`); + }, + + appendViaStream(fd) { + console.log(`[filesystem] APPEND STREAM ${fd}`); + }, + + advise(fd, offset, length, advice) { + console.log(`[filesystem] ADVISE`, fd, offset, length, advice); + }, + + syncData(fd) { + console.log(`[filesystem] SYNC DATA ${fd}`); + }, + + getFlags(fd) { + console.log(`[filesystem] FLAGS FOR ${fd}`); + }, + + getType(fd) { + let type = _getDescriptorType(fd); + if (type === null) { + filesystem.stat(fd); + type = _getDescriptorType(fd); + } + return type; + }, + + setFlags(fd, flags) { + console.log(`[filesystem] SET FLAGS ${fd} ${JSON.stringify(flags)}`); + }, + + setSize(fd, size) { + console.log(`[filesystem] SET SIZE`, fd, size); + }, + + setTimes(fd, dataAccessTimestamp, dataModificationTimestamp) { + console.log(`[filesystem] SET TIMES`, fd, dataAccessTimestamp, dataModificationTimestamp); + }, + + read(fd, length, offset) { + console.log(`[filesystem] READ`, fd, length, offset); + }, + + write(fd, buffer, offset) { + console.log(`[filesystem] WRITE`, fd, buffer, offset); + }, + + readDirectory(fd) { + const fullPath = _getFullPath(fd); + let dirs; + try { + dirs = readdirSync(fullPath, { withFileTypes: true }); + } + catch (e) { + _convertFsError(e); + } + _dirStreams.push({ fd, dirs, cursor: 0 }); + return _dirStreams.length - 1; + }, + + sync(fd) { + console.log(`[filesystem] SYNC`, fd); + }, + + createDirectoryAt(fd, path) { + console.log(`[filesystem] CREATE DIRECTORY`, fd, path); + }, + + stat(fd) { + let stats; + try { + stats = fstatSync(fd, { bigint: true }); + } + catch (e) { + _convertFsError(e); + } + const type = _lookupType(stats); + _setDescriptorType(fd, type); + return { + device: stats.dev, + inode: stats.ino, + type, + linkCount: stats.nlink, + size: stats.size, + dataAccessTimestamp: nsToDateTime(stats.atimeNs), + dataModificationTimestamp: nsToDateTime(stats.mtimeNs), + statusChangeTimestamp: nsToDateTime(stats.ctimeNs), + }; + }, + + statAt(fd, { symlinkFollow }, path) { + const fullPath = _descriptors[fd].path + path; + let stats; + try { + stats = (symlinkFollow ? statSync : lstatSync)(fullPath, { bigint: true }); + } + catch (e) { + _convertFsError(e); + } + const type = _lookupType(stats); + _setSubdescriptorType(fd, path, type); + return { + device: stats.dev, + inode: stats.ino, + type, + linkCount: stats.nlink, + size: stats.size, + dataAccessTimestamp: nsToDateTime(stats.atimeNs), + dataModificationTimestamp: nsToDateTime(stats.mtimeNs), + statusChangeTimestamp: nsToDateTime(stats.ctimeNs), + }; + }, + + setTimesAt(fd) { + console.log(`[filesystem] SET TIMES AT`, fd); + }, + + linkAt(fd) { + console.log(`[filesystem] LINK AT`, fd); + }, + + openAt(fd, pathFlags, path, openFlags, descriptorFlags, modes) { + // TODO + // if (pathFlags.symlinkFollow) { + // // resolve symlink + // } + const fullPath = _descriptors[fd].path + path; + let fsOpenFlags = 0x0; + if (openFlags.create) + fsOpenFlags |= constants.O_CREAT; + if (openFlags.directory) + fsOpenFlags |= constants.O_DIRECTORY; + if (openFlags.exclusive) + fsOpenFlags |= constants.O_EXCL; + if (openFlags.truncate) + fsOpenFlags |= constants.O_TRUNC; + if (descriptorFlags.read && descriptorFlags.write) + fsOpenFlags |= constants.O_RDWR; + else if (descriptorFlags.write) + fsOpenFlags |= constants.O_WRONLY; + // if (descriptorFlags.fileIntegritySync) + // if (descriptorFlags.dataIntegritySync) + // if (descriptorFlags.requestedWriteSync) + // if (descriptorFlags.mutateDirectory) + let fsMode = 0x0; + if (modes.readable) + fsMode |= 0o444; + if (modes.writeable) + fsMode |= 0o222; + if (modes.executable) + fsMode |= 0o111; + let localFd; + try { + localFd = openSync(fullPath, fsOpenFlags, fsMode); + } + catch (e) { + _convertFsError(e); + } + _addOpenedDescriptor(localFd, path, fd); + return localFd; + }, + + readlinkAt(fd) { + console.log(`[filesystem] READLINK AT`, fd); + }, + + removeDirectoryAt(fd) { + console.log(`[filesystem] REMOVE DIR AT`, fd); + }, + + renameAt(fd) { + console.log(`[filesystem] RENAME AT`, fd); + }, + + symlinkAt(fd) { + console.log(`[filesystem] SYMLINK AT`, fd); + }, + + unlinkFileAt(fd) { + console.log(`[filesystem] UNLINK FILE AT`, fd); + }, + + changeFilePermissionsAt(fd) { + console.log(`[filesystem] CHANGE FILE PERMISSIONS AT`, fd); + }, + + changeDirectoryPermissionsAt(fd) { + console.log(`[filesystem] CHANGE DIR PERMISSIONS AT`, fd); + }, + + lockShared(fd) { + console.log(`[filesystem] LOCK SHARED`, fd); + }, + + lockExclusive(fd) { + console.log(`[filesystem] LOCK EXCLUSIVE`, fd); + }, + + tryLockShared(fd) { + console.log(`[filesystem] TRY LOCK SHARED`, fd); + }, + + tryLockExclusive(fd) { + console.log(`[filesystem] TRY LOCK EXCLUSIVE`, fd); + }, + + unlock(fd) { + console.log(`[filesystem] UNLOCK`, fd); + }, + + dropDescriptor(fd) { + _removeOpenedDescriptor(fd); + closeSync(fd); + }, + + readDirectoryEntry(stream) { + const streamValue = _dirStreams[stream]; + if (streamValue.cursor === streamValue.dirs.length) + return null; + const dir = streamValue.dirs[streamValue.cursor++]; + const type = _lookupType(dir); + _setSubdescriptorType(streamValue.fd, '/' + dir.name, type); + return { inode: null, type, name: dir.name }; + }, + + dropDirectoryEntryStream(stream) { + _dirStreams.splice(stream, 1); } - catch (e) { - _convertFsError(e); - } - const type = _lookupType(stats); - _setDescriptorType(fd, type); - return { - device: stats.dev, - inode: stats.ino, - type, - linkCount: stats.nlink, - size: stats.size, - dataAccessTimestamp: nsToDateTime(stats.atimeNs), - dataModificationTimestamp: nsToDateTime(stats.mtimeNs), - statusChangeTimestamp: nsToDateTime(stats.ctimeNs), - }; -} - -export function statAt(fd, { symlinkFollow }, path) { - const fullPath = _descriptors[fd].path + path; - let stats; - try { - stats = (symlinkFollow ? statSync : lstatSync)(fullPath, { bigint: true }); - } - catch (e) { - _convertFsError(e); - } - const type = _lookupType(stats); - _setSubdescriptorType(fd, path, type); - return { - device: stats.dev, - inode: stats.ino, - type, - linkCount: stats.nlink, - size: stats.size, - dataAccessTimestamp: nsToDateTime(stats.atimeNs), - dataModificationTimestamp: nsToDateTime(stats.mtimeNs), - statusChangeTimestamp: nsToDateTime(stats.ctimeNs), - }; -} +}; -export function setTimesAt(fd) { - console.log(`[filesystem] SET TIMES AT`, fd); -} - -export function linkAt(fd) { - console.log(`[filesystem] LINK AT`, fd); -} - -export function openAt(fd, pathFlags, path, openFlags, descriptorFlags, modes) { - // TODO - // if (pathFlags.symlinkFollow) { - // // resolve symlink - // } - const fullPath = _descriptors[fd].path + path; - let fsOpenFlags = 0x0; - if (openFlags.create) - fsOpenFlags |= constants.O_CREAT; - if (openFlags.directory) - fsOpenFlags |= constants.O_DIRECTORY; - if (openFlags.exclusive) - fsOpenFlags |= constants.O_EXCL; - if (openFlags.truncate) - fsOpenFlags |= constants.O_TRUNC; - if (descriptorFlags.read && descriptorFlags.write) - fsOpenFlags |= constants.O_RDWR; - else if (descriptorFlags.write) - fsOpenFlags |= constants.O_WRONLY; - // if (descriptorFlags.fileIntegritySync) - // if (descriptorFlags.dataIntegritySync) - // if (descriptorFlags.requestedWriteSync) - // if (descriptorFlags.mutateDirectory) - let fsMode = 0x0; - if (modes.readable) - fsMode |= 0o444; - if (modes.writeable) - fsMode |= 0o222; - if (modes.executable) - fsMode |= 0o111; - let localFd; - try { - localFd = openSync(fullPath, fsOpenFlags, fsMode); - } - catch (e) { - _convertFsError(e); - } - _addOpenedDescriptor(localFd, path, fd); - return localFd; -} - -export function readlinkAt(fd) { - console.log(`[filesystem] READLINK AT`, fd); -} - -export function removeDirectoryAt(fd) { - console.log(`[filesystem] REMOVE DIR AT`, fd); -} - -export function renameAt(fd) { - console.log(`[filesystem] RENAME AT`, fd); -} - -export function symlinkAt(fd) { - console.log(`[filesystem] SYMLINK AT`, fd); -} - -export function unlinkFileAt(fd) { - console.log(`[filesystem] UNLINK FILE AT`, fd); -} - -export function changeFilePermissionsAt(fd) { - console.log(`[filesystem] CHANGE FILE PERMISSIONS AT`, fd); -} - -export function changeDirectoryPermissionsAt(fd) { - console.log(`[filesystem] CHANGE DIR PERMISSIONS AT`, fd); -} - -export function lockShared(fd) { - console.log(`[filesystem] LOCK SHARED`, fd); -} - -export function lockExclusive(fd) { - console.log(`[filesystem] LOCK EXCLUSIVE`, fd); -} - -export function tryLockShared(fd) { - console.log(`[filesystem] TRY LOCK SHARED`, fd); -} - -export function tryLockExclusive(fd) { - console.log(`[filesystem] TRY LOCK EXCLUSIVE`, fd); -} - -export function unlock(fd) { - console.log(`[filesystem] UNLOCK`, fd); -} - -export function dropDescriptor(fd) { - _removeOpenedDescriptor(fd); - closeSync(fd); -} - -export function readDirectoryEntry(stream) { - const streamValue = _dirStreams[stream]; - if (streamValue.cursor === streamValue.dirs.length) - return null; - const dir = streamValue.dirs[streamValue.cursor++]; - const type = _lookupType(dir); - _setSubdescriptorType(streamValue.fd, '/' + dir.name, type); - return { inode: null, type, name: dir.name }; -} - -export function dropDirectoryEntryStream(stream) { - _dirStreams.splice(stream, 1); -} +export { filesystem as filesystemFilesystem } diff --git a/packages/preview2-shim/lib/nodejs/http.js b/packages/preview2-shim/lib/nodejs/http.js index cedef0c69..fa5368dbb 100644 --- a/packages/preview2-shim/lib/nodejs/http.js +++ b/packages/preview2-shim/lib/nodejs/http.js @@ -17,3 +17,123 @@ export function send(req) { } throw new UnexpectedError(response); } + +export const httpIncomingHandler = { + handle () { + + } +}; + +export const httpOutgoingHandler = { + handle () { + + } +}; + +export const httpTypes = { + dropFields(_fields) { + console.log("[types] Drop fields"); + }, + newFields(_entries) { + console.log("[types] New fields"); + }, + fieldsGet(_fields, _name) { + console.log("[types] Fields get"); + }, + fieldsSet(_fields, _name, _value) { + console.log("[types] Fields set"); + }, + fieldsDelete(_fields, _name) { + console.log("[types] Fields delete"); + }, + fieldsAppend(_fields, _name, _value) { + console.log("[types] Fields append"); + }, + fieldsEntries(_fields) { + console.log("[types] Fields entries"); + }, + fieldsClone(_fields) { + console.log("[types] Fields clone"); + }, + finishIncomingStream(s) { + console.log(`[types] Finish incoming stream ${s}`); + }, + finishOutgoingStream(s, _trailers) { + console.log(`[types] Finish outgoing stream ${s}`); + }, + dropIncomingRequest(_req) { + console.log("[types] Drop incoming request"); + }, + dropOutgoingRequest(_req) { + console.log("[types] Drop outgoing request"); + }, + incomingRequestMethod(_req) { + console.log("[types] Incoming request method"); + }, + incomingRequestPath(_req) { + console.log("[types] Incoming request path"); + }, + incomingRequestQuery(_req) { + console.log("[types] Incoming request query"); + }, + incomingRequestScheme(_req) { + console.log("[types] Incoming request scheme"); + }, + incomingRequestAuthority(_req) { + console.log("[types] Incoming request authority"); + }, + incomingRequestHeaders(_req) { + console.log("[types] Incoming request headers"); + }, + incomingRequestConsume(_req) { + console.log("[types] Incoming request consume"); + }, + newOutgoingRequest(_method, _path, _query, _scheme, _authority, _headers) { + console.log("[types] New outgoing request"); + }, + outgoingRequestWrite(_req) { + console.log("[types] Outgoing request write"); + }, + dropResponseOutparam(_res) { + console.log("[types] Drop response outparam"); + }, + setResponseOutparam(_response) { + console.log("[types] Drop fields"); + }, + dropIncomingResponse(_res) { + console.log("[types] Drop incoming response"); + }, + dropOutgoingResponse(_res) { + console.log("[types] Drop outgoing response"); + }, + incomingResponseStatus(_res) { + console.log("[types] Incoming response status"); + }, + incomingResponseHeaders(_res) { + console.log("[types] Incoming response headers"); + }, + incomingResponseConsume(_res) { + console.log("[types] Incoming response consume"); + }, + newOutgoingResponse(_statusCode, _headers) { + console.log("[types] New outgoing response"); + }, + outgoingResponseWrite(_res) { + console.log("[types] Outgoing response write"); + }, + dropFutureIncomingResponse(_f) { + console.log("[types] Drop future incoming response"); + }, + futureIncomingResponseGet(_f) { + console.log("[types] Future incoming response get"); + }, + listenToFutureIncomingResponse(_f) { + console.log("[types] Listen to future incoming response"); + } +}; + +export { + httpIncomingHandler as incomingHandler, + httpOutgoingHandler as outgoingHandler, + httpTypes as types +} diff --git a/packages/preview2-shim/lib/nodejs/index.js b/packages/preview2-shim/lib/nodejs/index.js index c7bc2cd6f..0935e25c6 100644 --- a/packages/preview2-shim/lib/nodejs/index.js +++ b/packages/preview2-shim/lib/nodejs/index.js @@ -1,45 +1,23 @@ -import * as console from "./console.js"; -import * as defaultOutgoingHttp from "./default-outgoing-HTTP.js"; -import * as environment from "./environment.js"; -import * as exit from "./exit.js"; +import * as clocks from "./clocks.js"; import * as filesystem from "./filesystem.js"; -import * as instanceNetwork from "./instance-network.js"; -import * as ipNameLookup from "./ip-name-lookup.js"; -import * as monotonicClock from "./monotonic-clock.js"; -import * as network from "./network.js"; +import * as http from "./http.js"; +import * as io from "./io.js"; +import * as logging from "./logging.js"; import * as poll from "./poll.js"; -import * as preopens from "./preopens.js"; import * as random from "./random.js"; -import * as streams from "./streams.js"; -import * as tcpCreateSocket from "./tcp-create-socket.js"; -import * as tcp from "./tcp.js"; -import * as timezone from "./timezone.js"; -import * as types from "./types.js"; -import * as udpCreateSocket from "./udp-create-socket.js"; -import * as udp from "./udp.js"; -import * as wallClock from "./wall-clock.js"; +import * as sockets from "./sockets.js"; +import * as cliBase from "./cli-base.js"; export const importObject = { - 'console': console, - 'default-outgoing-HTTP': defaultOutgoingHttp, - 'environment': environment, - 'exit': exit, - 'filesystem': filesystem, - 'instance-network': instanceNetwork, - 'ip-name-lookup': ipNameLookup, - 'monotonic-clock': monotonicClock, - 'network': network, - 'poll': poll, - 'preopens': preopens, - 'random': random, - 'streams': streams, - 'tcp-create-socket': tcpCreateSocket, - 'tcp': tcp, - 'timezone': timezone, - 'types': types, - 'udp-create-socket': udpCreateSocket, - 'udp': udp, - 'wall-clock': wallClock + clocks, + filesystem, + http, + io, + logging, + poll, + random, + sockets, + cliBase, }; export { WasiHttp } from "../http/wasi-http.js"; diff --git a/packages/preview2-shim/lib/nodejs/instance-network.js b/packages/preview2-shim/lib/nodejs/instance-network.js deleted file mode 100644 index 8c108b824..000000000 --- a/packages/preview2-shim/lib/nodejs/instance-network.js +++ /dev/null @@ -1,3 +0,0 @@ -export function instanceNetwork () { - -} diff --git a/packages/preview2-shim/lib/nodejs/io.js b/packages/preview2-shim/lib/nodejs/io.js index a1babc9e0..e008f8833 100644 --- a/packages/preview2-shim/lib/nodejs/io.js +++ b/packages/preview2-shim/lib/nodejs/io.js @@ -1,25 +1,144 @@ -export function read(s, len) { - switch (s) { - case 0: - return [process.stdin.read(len), true]; - default: - throw new Error(`TODO: write ${s}`); +import { readSync as fsReadSync } from 'node:fs'; + +function _convertFsError (e) { + switch (e.code) { + case 'EACCES': throw 'access'; + case 'EAGAIN': + case 'EWOULDBLOCK': throw 'would-block'; + case 'EALREADY': throw 'already'; + case 'EBADF': throw 'bad-descriptor'; + case 'EBUSY': throw 'busy'; + case 'EDEADLK': throw 'deadlock'; + case 'EDQUOT': throw 'quota'; + case 'EEXIST': throw 'exist'; + case 'EFBIG': throw 'file-too-large'; + case 'EILSEQ': throw 'illegal-byte-sequence'; + case 'EINPROGRESS': throw 'in-progress'; + case 'EINTR': throw 'interrupted'; + case 'EINVAL': throw 'invalid'; + case 'EIO': throw 'io'; + case 'EISDIR': throw 'is-directory'; + case 'ELOOP': throw 'loop'; + case 'EMLINK': throw 'too-many-links'; + case 'EMSGSIZE': throw 'message-size'; + case 'ENAMETOOLONG': throw 'name-too-long' + case 'ENODEV': throw 'no-device'; + case 'ENOENT': throw 'no-entry'; + case 'ENOLCK': throw 'no-lock'; + case 'ENOMEM': throw 'insufficient-memory'; + case 'ENOSPC': throw 'insufficient-space'; + case 'ENOTDIR': throw 'not-directory'; + case 'ENOTEMPTY': throw 'not-empty'; + case 'ENOTRECOVERABLE': throw 'not-recoverable'; + case 'ENOTSUP': throw 'unsupported'; + case 'ENOTTY': throw 'no-tty'; + case 'ENXIO': throw 'no-such-device'; + case 'EOVERFLOW': throw 'overflow'; + case 'EPERM': throw 'not-permitted'; + case 'EPIPE': throw 'pipe'; + case 'EROFS': throw 'read-only'; + case 'ESPIPE': throw 'invalid-seek'; + case 'ETXTBSY': throw 'text-file-busy'; + case 'EXDEV': throw 'cross-device'; + default: throw e; } } -export function write(s, buf) { - switch (s) { - case 0: - throw new Error(`TODO: write stdin`); - case 1: { - process.stdout.write(buf); - return BigInt(buf.byteLength); +export let _streams = {}; +let streamCnt = 0; +export function _createFileStream(fd, offset) { + // note we only support offset 0 + if (Number(offset) === 0) + _streams[streamCnt] = { + type: 'file', + fd: fd + }; + return streamCnt++; +} + +export const ioStreams = { + read(s, len) { + switch (s) { + case 0: + return [process.stdin.read(len), true]; + default: + throw new Error(`TODO: write ${s}`); } - case 2: { - process.stderr.write(buf); - return BigInt(buf.byteLength); + }, + blockingRead(s, len) { + len = Number(len); + const stream = _streams[s]; + if (!stream) throw null; + switch (stream.type) { + case 'file': { + const buf = Buffer.alloc(Number(len)); + try { + const readBytes = fsReadSync(stream.fd, buf, 0, Number(len)); + if (readBytes < Number(len)) + return [new Uint8Array(), true]; + return [new Uint8Array(buf.buffer, 0, readBytes), false]; + } + catch (e) { + _convertFsError(e); + } + break; + } + default: throw null; } - default: - throw new Error(`TODO: write ${s}`); + }, + skip(s, _len) { + console.log(`[streams] Skip ${s}`); + }, + blockingSkip(s, _len) { + console.log(`[streams] Blocking skip ${s}`); + }, + subscribeToInputStream(s) { + console.log(`[streams] Subscribe to input stream ${s}`); + }, + dropInputStream(s) { + delete _streams[s]; + + }, + write(s, buf) { + switch (s) { + case 0: + throw new Error(`TODO: write stdin`); + case 1: { + process.stdout.write(buf); + return BigInt(buf.byteLength); + } + case 2: { + process.stderr.write(buf); + return BigInt(buf.byteLength); + } + default: + throw new Error(`TODO: write ${s}`); + } + }, + blockingWrite(s, _buf) { + console.log(`[streams] Blocking write ${s}`); + }, + writeZeroes(s, _len) { + console.log(`[streams] Write zeroes ${s}`); + }, + blockingWriteZeroes(s, _len) { + console.log(`[streams] Blocking write zeroes ${s}`); + }, + splice(s, _src, _len) { + console.log(`[streams] Splice ${s}`); + }, + blockingSplice(s, _src, _len) { + console.log(`[streams] Blocking splice ${s}`); + }, + forward(s, _src) { + console.log(`[streams] Forward ${s}`); + }, + subscribeToOutputStream(s) { + console.log(`[streams] Subscribe to output stream ${s}`); + }, + dropOutputStream(s) { + console.log(`[streams] Drop output stream ${s}`); } -} +}; + +export { ioStreams as streams } diff --git a/packages/preview2-shim/lib/nodejs/ip-name-lookup.js b/packages/preview2-shim/lib/nodejs/ip-name-lookup.js deleted file mode 100644 index e1a449704..000000000 --- a/packages/preview2-shim/lib/nodejs/ip-name-lookup.js +++ /dev/null @@ -1,23 +0,0 @@ -export function dropResolveAddressStream () { - -} - -export function subscribe () { - -} - -export function resolveAddresses () { - -} - -export function resolveNextAddress () { - -} - -export function nonBlocking () { - -} - -export function setNonBlocking () { - -} diff --git a/packages/preview2-shim/lib/nodejs/logging.js b/packages/preview2-shim/lib/nodejs/logging.js new file mode 100644 index 000000000..25ae1d76d --- /dev/null +++ b/packages/preview2-shim/lib/nodejs/logging.js @@ -0,0 +1,16 @@ +const levels = ["trace", "debug", "info", "warn", "error"]; + +let logLevel = levels.indexOf("warn"); + +export const loggingHandler = { + log(level, context, msg) { + if (logLevel > levels.indexOf(level)) return; + process.stdout.write(`${level}: (${context}) ${msg}\n`); + } +}; + +export function setLevel(level) { + logLevel = levels.indexOf(level); +} + +export { loggingHandler as handler } diff --git a/packages/preview2-shim/lib/nodejs/monotonic-clock.js b/packages/preview2-shim/lib/nodejs/monotonic-clock.js deleted file mode 100644 index 932fd83d4..000000000 --- a/packages/preview2-shim/lib/nodejs/monotonic-clock.js +++ /dev/null @@ -1,14 +0,0 @@ -import { hrtime } from "node:process"; - -export function resolution () { - return 1n; -} - -let _hrStart = hrtime.bigint(); -export function now () { - return hrtime.bigint() - _hrStart; -} - -export function subscribe (_when, _absolute) { - console.log(`[monotonic-clock] Subscribe`); -} diff --git a/packages/preview2-shim/lib/nodejs/network.js b/packages/preview2-shim/lib/nodejs/network.js deleted file mode 100644 index 0f1f0ea0d..000000000 --- a/packages/preview2-shim/lib/nodejs/network.js +++ /dev/null @@ -1,3 +0,0 @@ -export function dropNetwork () { - -} diff --git a/packages/preview2-shim/lib/nodejs/poll.js b/packages/preview2-shim/lib/nodejs/poll.js index 60dc3c05c..e66f24beb 100644 --- a/packages/preview2-shim/lib/nodejs/poll.js +++ b/packages/preview2-shim/lib/nodejs/poll.js @@ -1,7 +1,11 @@ -export function dropPollable(p) { - console.log(`[poll] Drop pollable ${p}`); -} +export const pollPoll = { + dropPollable (pollable) { + console.log(`[poll] Drop (${pollable})`); + }, + pollOneoff (input) { + console.log(`[poll] Oneoff (${input})`); + return []; + } +}; -export function pollOneoff(f) { - console.log(`[poll] Poll oneoff ${f}`); -} +export { pollPoll as poll } diff --git a/packages/preview2-shim/lib/nodejs/random.js b/packages/preview2-shim/lib/nodejs/random.js index 66aa9dddc..4a1e60c17 100644 --- a/packages/preview2-shim/lib/nodejs/random.js +++ b/packages/preview2-shim/lib/nodejs/random.js @@ -1,18 +1,23 @@ import { randomBytes } from "node:crypto"; -export function getRandomBytes(len) { - return randomBytes(Number(len)); -} +let insecureRandomValue1, insecureRandomValue2; -export function getRandomU64 () { - return new BigUint64Array(randomBytes(8).buffer)[0]; -} +export const randomRandom = { + getRandomBytes(len) { + return randomBytes(Number(len)); + }, -let insecureRandomValue1, insecureRandomValue2; -export function insecureRandom () { - if (insecureRandomValue1 === undefined) { - insecureRandomValue1 = getRandomU64(); - insecureRandomValue2 = getRandomU64(); + getRandomU64 () { + return new BigUint64Array(randomBytes(8).buffer)[0]; + }, + + insecureRandom () { + if (insecureRandomValue1 === undefined) { + insecureRandomValue1 = randomRandom.getRandomU64(); + insecureRandomValue2 = randomRandom.getRandomU64(); + } + return [insecureRandomValue1, insecureRandomValue2]; } - return [insecureRandomValue1, insecureRandomValue2]; -} +}; + +export { randomRandom as random } diff --git a/packages/preview2-shim/lib/nodejs/sockets.js b/packages/preview2-shim/lib/nodejs/sockets.js new file mode 100644 index 000000000..210e6ccb3 --- /dev/null +++ b/packages/preview2-shim/lib/nodejs/sockets.js @@ -0,0 +1,203 @@ +export const socketsInstanceNetwork = { + instanceNetwork () { + console.log(`[sockets] instance network`); + } +}; + +export const socketsIpNameLookup = { + dropResolveAddressStream () { + + }, + subscribe () { + + }, + resolveAddresses () { + + }, + resolveNextAddress () { + + }, + nonBlocking () { + + }, + setNonBlocking () { + + }, +}; + +export const socketsNetwork = { + dropNetwork () { + + } +}; + +export const socketsTcpCreateSocket = { + createTcpSocket () { + + } +}; + +export const socketsTcp = { + subscribe () { + + }, + dropTcpSocket() { + + }, + bind() { + + }, + connect() { + + }, + listen() { + + }, + accept() { + + }, + localAddress() { + + }, + remoteAddress() { + + }, + addressFamily() { + + }, + ipv6Only() { + + }, + setIpv6Only() { + + }, + setListenBacklogSize() { + + }, + keepAlive() { + + }, + setKeepAlive() { + + }, + noDelay() { + + }, + setNoDelay() { + + }, + unicastHopLimit() { + + }, + setUnicastHopLimit() { + + }, + receiveBufferSize() { + + }, + setReceiveBufferSize() { + + }, + sendBufferSize() { + + }, + setSendBufferSize() { + + }, + nonBlocking() { + + }, + setNonBlocking() { + + }, + shutdown() { + + } +}; + +export const socketsUdp = { + subscribe () { + + }, + + dropUdpSocket () { + + }, + + bind () { + + }, + + connect () { + + }, + + receive () { + + }, + + send () { + + }, + + localAddress () { + + }, + + remoteAddress () { + + }, + + addressFamily () { + + }, + + ipv6Only () { + + }, + + setIpv6Only () { + + }, + + unicastHopLimit () { + + }, + + setUnicastHopLimit () { + + }, + + receiveBufferSize () { + + }, + + setReceiveBufferSize () { + + }, + + sendBufferSize () { + + }, + + setSendBufferSize () { + + }, + + nonBlocking () { + + }, + + setNonBlocking () { + + } +}; + +export { + socketsInstanceNetwork as instanceNetwork, + socketsIpNameLookup as ipNameLookup, + socketsNetwork as network, + socketsTcpCreateSocket as tcpCreateSocket, + socketsTcp as tcp, + socketsUdp as udp +} diff --git a/packages/preview2-shim/lib/nodejs/stderr.js b/packages/preview2-shim/lib/nodejs/stderr.js deleted file mode 100644 index 84153c2d3..000000000 --- a/packages/preview2-shim/lib/nodejs/stderr.js +++ /dev/null @@ -1,4 +0,0 @@ -// TODO: remove -export function print(message) { - process.stderr.write(message); -} diff --git a/packages/preview2-shim/lib/nodejs/streams.js b/packages/preview2-shim/lib/nodejs/streams.js deleted file mode 100644 index 312e33b11..000000000 --- a/packages/preview2-shim/lib/nodejs/streams.js +++ /dev/null @@ -1,97 +0,0 @@ -import { readSync as fsReadSync } from 'node:fs'; -import { _convertFsError } from './filesystem.js'; - -export let _streams = {}; -let streamCnt = 0; -export function _createFileStream(fd, offset) { - // note we only support offset 0 - if (Number(offset) === 0) - _streams[streamCnt] = { - type: 'file', - fd: fd - }; - return streamCnt++; -} - -export function read(s, len) { - switch (s) { - case 0: - return [process.stdin.read(len), true]; - default: - throw new Error(`TODO: write ${s}`); - } -} -export function blockingRead(s, len) { - len = Number(len); - const stream = _streams[s]; - if (!stream) throw null; - switch (stream.type) { - case 'file': { - const buf = Buffer.alloc(Number(len)); - try { - const readBytes = fsReadSync(stream.fd, buf, 0, Number(len)); - if (readBytes < Number(len)) - return [new Uint8Array(), true]; - return [new Uint8Array(buf.buffer, 0, readBytes), false]; - } - catch (e) { - _convertFsError(e); - } - break; - } - default: throw null; - } -} -export function skip(s, _len) { - console.log(`[streams] Skip ${s}`); -} -export function blockingSkip(s, _len) { - console.log(`[streams] Blocking skip ${s}`); -} -export function subscribeToInputStream(s) { - console.log(`[streams] Subscribe to input stream ${s}`); -} -export function dropInputStream(s) { - delete _streams[s]; - -} -export function write(s, buf) { - switch (s) { - case 0: - throw new Error(`TODO: write stdin`); - case 1: { - process.stdout.write(buf); - return BigInt(buf.byteLength); - } - case 2: { - process.stderr.write(buf); - return BigInt(buf.byteLength); - } - default: - throw new Error(`TODO: write ${s}`); - } -} -export function blockingWrite(s, _buf) { - console.log(`[streams] Blocking write ${s}`); -} -export function writeZeroes(s, _len) { - console.log(`[streams] Write zeroes ${s}`); -} -export function blockingWriteZeroes(s, _len) { - console.log(`[streams] Blocking write zeroes ${s}`); -} -export function splice(s, _src, _len) { - console.log(`[streams] Splice ${s}`); -} -export function blockingSplice(s, _src, _len) { - console.log(`[streams] Blocking splice ${s}`); -} -export function forward(s, _src) { - console.log(`[streams] Forward ${s}`); -} -export function subscribeToOutputStream(s) { - console.log(`[streams] Subscribe to output stream ${s}`); -} -export function dropOutputStream(s) { - console.log(`[streams] Drop output stream ${s}`); -} diff --git a/packages/preview2-shim/lib/nodejs/tcp-create-socket.js b/packages/preview2-shim/lib/nodejs/tcp-create-socket.js deleted file mode 100644 index 0cf6401dd..000000000 --- a/packages/preview2-shim/lib/nodejs/tcp-create-socket.js +++ /dev/null @@ -1,3 +0,0 @@ -export function createTcpSocket () { - -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/nodejs/tcp.js b/packages/preview2-shim/lib/nodejs/tcp.js deleted file mode 100644 index 45dbf9d98..000000000 --- a/packages/preview2-shim/lib/nodejs/tcp.js +++ /dev/null @@ -1,75 +0,0 @@ -export function subscribe () { - -} -export function dropTcpSocket() { - -} -export function bind() { - -} -export function connect() { - -} -export function listen() { - -} -export function accept() { - -} -export function localAddress() { - -} -export function remoteAddress() { - -} -export function addressFamily() { - -} -export function ipv6Only() { - -} -export function setIpv6Only() { - -} -export function setListenBacklogSize() { - -} -export function keepAlive() { - -} -export function setKeepAlive() { - -} -export function noDelay() { - -} -export function setNoDelay() { - -} -export function unicastHopLimit() { - -} -export function setUnicastHopLimit() { - -} -export function receiveBufferSize() { - -} -export function setReceiveBufferSize() { - -} -export function sendBufferSize() { - -} -export function setSendBufferSize() { - -} -export function nonBlocking() { - -} -export function setNonBlocking() { - -} -export function shutdown() { - -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/nodejs/timezone.js b/packages/preview2-shim/lib/nodejs/timezone.js deleted file mode 100644 index 7568396f0..000000000 --- a/packages/preview2-shim/lib/nodejs/timezone.js +++ /dev/null @@ -1,12 +0,0 @@ -export function display (timezone, when) { - console.log(`[timezone] DISPLAY ${timezone} ${when}`); -} - -export function utcOffset (timezone, when) { - console.log(`[timezone] UTC OFFSET ${timezone} ${when}`); - return 0; -} - -export function dropTimezone (timezone) { - console.log(`[timezone] DROP ${timezone}`); -} diff --git a/packages/preview2-shim/lib/nodejs/types.js b/packages/preview2-shim/lib/nodejs/types.js deleted file mode 100644 index 722d31c59..000000000 --- a/packages/preview2-shim/lib/nodejs/types.js +++ /dev/null @@ -1,99 +0,0 @@ -export function dropFields(_fields) { - console.log("[types] Drop fields"); -} -export function newFields(_entries) { - console.log("[types] New fields"); -} -export function fieldsGet(_fields, _name) { - console.log("[types] Fields get"); -} -export function fieldsSet(_fields, _name, _value) { - console.log("[types] Fields set"); -} -export function fieldsDelete(_fields, _name) { - console.log("[types] Fields delete"); -} -export function fieldsAppend(_fields, _name, _value) { - console.log("[types] Fields append"); -} -export function fieldsEntries(_fields) { - console.log("[types] Fields entries"); -} -export function fieldsClone(_fields) { - console.log("[types] Fields clone"); -} -export function finishIncomingStream(s) { - console.log(`[types] Finish incoming stream ${s}`); -} -export function finishOutgoingStream(s, _trailers) { - console.log(`[types] Finish outgoing stream ${s}`); -} -export function dropIncomingRequest(_req) { - console.log("[types] Drop incoming request"); -} -export function dropOutgoingRequest(_req) { - console.log("[types] Drop outgoing request"); -} -export function incomingRequestMethod(_req) { - console.log("[types] Incoming request method"); -} -export function incomingRequestPath(_req) { - console.log("[types] Incoming request path"); -} -export function incomingRequestQuery(_req) { - console.log("[types] Incoming request query"); -} -export function incomingRequestScheme(_req) { - console.log("[types] Incoming request scheme"); -} -export function incomingRequestAuthority(_req) { - console.log("[types] Incoming request authority"); -} -export function incomingRequestHeaders(_req) { - console.log("[types] Incoming request headers"); -} -export function incomingRequestConsume(_req) { - console.log("[types] Incoming request consume"); -} -export function newOutgoingRequest(_method, _path, _query, _scheme, _authority, _headers) { - console.log("[types] New outgoing request"); -} -export function outgoingRequestWrite(_req) { - console.log("[types] Outgoing request write"); -} -export function dropResponseOutparam(_res) { - console.log("[types] Drop response outparam"); -} -export function setResponseOutparam(_response) { - console.log("[types] Drop fields"); -} -export function dropIncomingResponse(_res) { - console.log("[types] Drop incoming response"); -} -export function dropOutgoingResponse(_res) { - console.log("[types] Drop outgoing response"); -} -export function incomingResponseStatus(_res) { - console.log("[types] Incoming response status"); -} -export function incomingResponseHeaders(_res) { - console.log("[types] Incoming response headers"); -} -export function incomingResponseConsume(_res) { - console.log("[types] Incoming response consume"); -} -export function newOutgoingResponse(_statusCode, _headers) { - console.log("[types] New outgoing response"); -} -export function outgoingResponseWrite(_res) { - console.log("[types] Outgoing response write"); -} -export function dropFutureIncomingResponse(_f) { - console.log("[types] Drop future incoming response"); -} -export function futureIncomingResponseGet(_f) { - console.log("[types] Future incoming response get"); -} -export function listenToFutureIncomingResponse(_f) { - console.log("[types] Listen to future incoming response"); -} diff --git a/packages/preview2-shim/lib/nodejs/udp-create-socket.js b/packages/preview2-shim/lib/nodejs/udp-create-socket.js deleted file mode 100644 index b0a86b496..000000000 --- a/packages/preview2-shim/lib/nodejs/udp-create-socket.js +++ /dev/null @@ -1,3 +0,0 @@ -export function createUdpSocket () { - -} diff --git a/packages/preview2-shim/lib/nodejs/udp.js b/packages/preview2-shim/lib/nodejs/udp.js deleted file mode 100644 index 9f47bc4d4..000000000 --- a/packages/preview2-shim/lib/nodejs/udp.js +++ /dev/null @@ -1,75 +0,0 @@ -export function subscribe () { - -} - -export function dropUdpSocket () { - -} - -export function bind () { - -} - -export function connect () { - -} - -export function receive () { - -} - -export function send () { - -} - -export function localAddress () { - -} - -export function remoteAddress () { - -} - -export function addressFamily () { - -} - -export function ipv6Only () { - -} - -export function setIpv6Only () { - -} - -export function unicastHopLimit () { - -} - -export function setUnicastHopLimit () { - -} - -export function receiveBufferSize () { - -} - -export function setReceiveBufferSize () { - -} - -export function sendBufferSize () { - -} - -export function setSendBufferSize () { - -} - -export function nonBlocking () { - -} - -export function setNonBlocking () { - -} \ No newline at end of file diff --git a/packages/preview2-shim/lib/nodejs/wall-clock.js b/packages/preview2-shim/lib/nodejs/wall-clock.js deleted file mode 100644 index 0ff3988f2..000000000 --- a/packages/preview2-shim/lib/nodejs/wall-clock.js +++ /dev/null @@ -1,9 +0,0 @@ -export function now() { - const seconds = BigInt(Math.floor(Date.now() / 1e3)); - const nanoseconds = (Date.now() % 1e3) * 1e6; - return { seconds, nanoseconds }; -} - -export function resolution() { - console.log(`[wall-clock] Wall clock resolution`); -} diff --git a/packages/preview2-shim/test/test.js b/packages/preview2-shim/test/test.js index b1e9308b7..deab6ad5e 100644 --- a/packages/preview2-shim/test/test.js +++ b/packages/preview2-shim/test/test.js @@ -3,6 +3,6 @@ import { ok } from 'node:assert'; suite('Node.js', async () => { test('Basic importing', async () => { const { default: wasiImport } = await import('@bytecodealliance/preview2-shim'); - ok(wasiImport['filesystem'].stat); + ok(wasiImport['filesystem'].filesystem.stat); }); }); diff --git a/packages/preview2-shim/types/exports/HTTP.d.ts b/packages/preview2-shim/types/exports/http-incoming-handler.d.ts similarity index 87% rename from packages/preview2-shim/types/exports/HTTP.d.ts rename to packages/preview2-shim/types/exports/http-incoming-handler.d.ts index 844504d8a..68472899b 100644 --- a/packages/preview2-shim/types/exports/HTTP.d.ts +++ b/packages/preview2-shim/types/exports/http-incoming-handler.d.ts @@ -1,4 +1,4 @@ -export namespace Http { +export namespace HttpIncomingHandler { export function handle(request: IncomingRequest, responseOut: ResponseOutparam): void; } import type { IncomingRequest } from '../imports/types'; diff --git a/packages/preview2-shim/types/imports/cli-base-environment.d.ts b/packages/preview2-shim/types/imports/cli-base-environment.d.ts new file mode 100644 index 000000000..cc8992eb9 --- /dev/null +++ b/packages/preview2-shim/types/imports/cli-base-environment.d.ts @@ -0,0 +1,17 @@ +export namespace CliBaseEnvironment { + /** + * Get the POSIX-style environment variables. + * + * Each environment variable is provided as a pair of string variable names + * and string value. + * + * Morally, these are a value import, but until value imports are available + * in the component model, this import function should return the same + * values each time it is called. + */ + export function getEnvironment(): [string, string][]; + /** + * Get the POSIX-style arguments to the program. + */ + export function getArguments(): string[]; +} diff --git a/packages/preview2-shim/types/imports/exit.d.ts b/packages/preview2-shim/types/imports/cli-base-exit.d.ts similarity index 57% rename from packages/preview2-shim/types/imports/exit.d.ts rename to packages/preview2-shim/types/imports/cli-base-exit.d.ts index 6bbe89687..ef0e388ee 100644 --- a/packages/preview2-shim/types/imports/exit.d.ts +++ b/packages/preview2-shim/types/imports/cli-base-exit.d.ts @@ -1,4 +1,7 @@ -export namespace Exit { +export namespace CliBaseExit { + /** + * Exit the curerent instance and any linked instances. + */ export function exit(status: Result): void; } export type Result = { tag: 'ok', val: T } | { tag: 'err', val: E }; diff --git a/packages/preview2-shim/types/imports/preopens.d.ts b/packages/preview2-shim/types/imports/cli-base-preopens.d.ts similarity index 62% rename from packages/preview2-shim/types/imports/preopens.d.ts rename to packages/preview2-shim/types/imports/cli-base-preopens.d.ts index 6a4a8c23c..a5432ad87 100644 --- a/packages/preview2-shim/types/imports/preopens.d.ts +++ b/packages/preview2-shim/types/imports/cli-base-preopens.d.ts @@ -1,15 +1,12 @@ -export namespace Preopens { - export function getStdio(): StdioPreopens; +export namespace CliBasePreopens { + /** + * Return the set of of preopened directories, and their path. + */ export function getDirectories(): [Descriptor, string][]; } +import type { Descriptor } from '../imports/filesystem'; +export { Descriptor }; import type { InputStream } from '../imports/streams'; export { InputStream }; import type { OutputStream } from '../imports/streams'; export { OutputStream }; -export interface StdioPreopens { - stdin: InputStream, - stdout: OutputStream, - stderr: OutputStream, -} -import type { Descriptor } from '../imports/filesystem'; -export { Descriptor }; diff --git a/packages/preview2-shim/types/imports/cli-base-stderr.d.ts b/packages/preview2-shim/types/imports/cli-base-stderr.d.ts new file mode 100644 index 000000000..66381437f --- /dev/null +++ b/packages/preview2-shim/types/imports/cli-base-stderr.d.ts @@ -0,0 +1,5 @@ +export namespace CliBaseStderr { + export function getStderr(): OutputStream; +} +import type { OutputStream } from '../imports/streams'; +export { OutputStream }; diff --git a/packages/preview2-shim/types/imports/cli-base-stdin.d.ts b/packages/preview2-shim/types/imports/cli-base-stdin.d.ts new file mode 100644 index 000000000..871840e94 --- /dev/null +++ b/packages/preview2-shim/types/imports/cli-base-stdin.d.ts @@ -0,0 +1,5 @@ +export namespace CliBaseStdin { + export function getStdin(): InputStream; +} +import type { InputStream } from '../imports/streams'; +export { InputStream }; diff --git a/packages/preview2-shim/types/imports/cli-base-stdout.d.ts b/packages/preview2-shim/types/imports/cli-base-stdout.d.ts new file mode 100644 index 000000000..85e63971e --- /dev/null +++ b/packages/preview2-shim/types/imports/cli-base-stdout.d.ts @@ -0,0 +1,5 @@ +export namespace CliBaseStdout { + export function getStdout(): OutputStream; +} +import type { OutputStream } from '../imports/streams'; +export { OutputStream }; diff --git a/packages/preview2-shim/types/imports/clocks-monotonic-clock.d.ts b/packages/preview2-shim/types/imports/clocks-monotonic-clock.d.ts new file mode 100644 index 000000000..c9e438f01 --- /dev/null +++ b/packages/preview2-shim/types/imports/clocks-monotonic-clock.d.ts @@ -0,0 +1,24 @@ +export namespace ClocksMonotonicClock { + /** + * Read the current value of the clock. + * + * The clock is monotonic, therefore calling this function repeatedly will + * produce a sequence of non-decreasing values. + */ + export function now(): Instant; + /** + * Query the resolution of the clock. + */ + export function resolution(): Instant; + /** + * Create a `pollable` which will resolve once the specified time has been + * reached. + */ + export function subscribe(when: Instant, absolute: boolean): Pollable; +} +import type { Pollable } from '../imports/poll'; +export { Pollable }; +/** + * A timestamp in nanoseconds. + */ +export type Instant = bigint; diff --git a/packages/preview2-shim/types/imports/clocks-timezone.d.ts b/packages/preview2-shim/types/imports/clocks-timezone.d.ts new file mode 100644 index 000000000..c134105e0 --- /dev/null +++ b/packages/preview2-shim/types/imports/clocks-timezone.d.ts @@ -0,0 +1,71 @@ +export namespace ClocksTimezone { + /** + * Return information needed to display the given `datetime`. This includes + * the UTC offset, the time zone name, and a flag indicating whether + * daylight saving time is active. + * + * If the timezone cannot be determined for the given `datetime`, return a + * `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + * saving time. + */ + export function display(this: Timezone, when: Datetime): TimezoneDisplay; + /** + * The same as `display`, but only return the UTC offset. + */ + export function utcOffset(this: Timezone, when: Datetime): number; + /** + * Dispose of the specified input-stream, after which it may no longer + * be used. + */ + export function dropTimezone(this: Timezone): void; +} +import type { Datetime } from '../imports/wall-clock'; +export { Datetime }; +/** + * Information useful for displaying the timezone of a specific `datetime`. + * + * This information may vary within a single `timezone` to reflect daylight + * saving time adjustments. + */ +export interface TimezoneDisplay { + /** + * The number of seconds difference between UTC time and the local + * time of the timezone. + * + * The returned value will always be less than 86400 which is the + * number of seconds in a day (24*60*60). + * + * In implementations that do not expose an actual time zone, this + * should return 0. + */ + utcOffset: number, + /** + * The abbreviated name of the timezone to display to a user. The name + * `UTC` indicates Coordinated Universal Time. Otherwise, this should + * reference local standards for the name of the time zone. + * + * In implementations that do not expose an actual time zone, this + * should be the string `UTC`. + * + * In time zones that do not have an applicable name, a formatted + * representation of the UTC offset may be returned, such as `-04:00`. + */ + name: string, + /** + * Whether daylight saving time is active. + * + * In implementations that do not expose an actual time zone, this + * should return false. + */ + inDaylightSavingTime: boolean, +} +/** + * A timezone. + * + * In timezones that recognize daylight saving time, also known as daylight + * time and summer time, the information returned from the functions varies + * over time to reflect these adjustments. + * + * This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + */ +export type Timezone = number; diff --git a/packages/preview2-shim/types/imports/clocks-wall-clock.d.ts b/packages/preview2-shim/types/imports/clocks-wall-clock.d.ts new file mode 100644 index 000000000..4faaab18e --- /dev/null +++ b/packages/preview2-shim/types/imports/clocks-wall-clock.d.ts @@ -0,0 +1,31 @@ +export namespace ClocksWallClock { + /** + * Read the current value of the clock. + * + * This clock is not monotonic, therefore calling this function repeatedly + * will not necessarily produce a sequence of non-decreasing values. + * + * The returned timestamps represent the number of seconds since + * 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + * also known as [Unix Time]. + * + * The nanoseconds field of the output is always less than 1000000000. + * + * [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + * [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + */ + export function now(): Datetime; + /** + * Query the resolution of the clock. + * + * The nanoseconds field of the output is always less than 1000000000. + */ + export function resolution(): Datetime; +} +/** + * A time and date in seconds plus nanoseconds. + */ +export interface Datetime { + seconds: bigint, + nanoseconds: number, +} diff --git a/packages/preview2-shim/types/imports/console.d.ts b/packages/preview2-shim/types/imports/console.d.ts deleted file mode 100644 index 836dc6288..000000000 --- a/packages/preview2-shim/types/imports/console.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -export namespace Console { - export function log(level: Level, context: string, message: string): void; -} -/** - * # Variants - * - * ## `"trace"` - * - * ## `"debug"` - * - * ## `"info"` - * - * ## `"warn"` - * - * ## `"error"` - */ -export type Level = 'trace' | 'debug' | 'info' | 'warn' | 'error'; diff --git a/packages/preview2-shim/types/imports/environment.d.ts b/packages/preview2-shim/types/imports/environment.d.ts deleted file mode 100644 index dd76cc3ce..000000000 --- a/packages/preview2-shim/types/imports/environment.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export namespace Environment { - export function getEnvironment(): [string, string][]; - export function getArguments(): string[]; -} diff --git a/packages/preview2-shim/types/imports/filesystem-filesystem.d.ts b/packages/preview2-shim/types/imports/filesystem-filesystem.d.ts new file mode 100644 index 000000000..d8e492623 --- /dev/null +++ b/packages/preview2-shim/types/imports/filesystem-filesystem.d.ts @@ -0,0 +1,857 @@ +export namespace FilesystemFilesystem { + /** + * Return a stream for reading from a file. + * + * Multiple read, write, and append streams may be active on the same open + * file and they do not interfere with each other. + * + * Note: This allows using `wasi:io/streams.read`, which is similar to `read` in POSIX. + */ + export function readViaStream(this: Descriptor, offset: Filesize): InputStream; + /** + * Return a stream for writing to a file. + * + * Note: This allows using `wasi:io/streams.write`, which is similar to `write` in + * POSIX. + */ + export function writeViaStream(this: Descriptor, offset: Filesize): OutputStream; + /** + * Return a stream for appending to a file. + * + * Note: This allows using `wasi:io/streams.write`, which is similar to `write` with + * `O_APPEND` in in POSIX. + */ + export function appendViaStream(this: Descriptor): OutputStream; + /** + * Provide file advisory information on a descriptor. + * + * This is similar to `posix_fadvise` in POSIX. + */ + export function advise(this: Descriptor, offset: Filesize, length: Filesize, advice: Advice): void; + /** + * Synchronize the data of a file to disk. + * + * This function succeeds with no effect if the file descriptor is not + * opened for writing. + * + * Note: This is similar to `fdatasync` in POSIX. + */ + export function syncData(this: Descriptor): void; + /** + * Get flags associated with a descriptor. + * + * Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + * + * Note: This returns the value that was the `fs_flags` value returned + * from `fdstat_get` in earlier versions of WASI. + */ + export function getFlags(this: Descriptor): DescriptorFlags; + /** + * Get the dynamic type of a descriptor. + * + * Note: This returns the same value as the `type` field of the `fd-stat` + * returned by `stat`, `stat-at` and similar. + * + * Note: This returns similar flags to the `st_mode & S_IFMT` value provided + * by `fstat` in POSIX. + * + * Note: This returns the value that was the `fs_filetype` value returned + * from `fdstat_get` in earlier versions of WASI. + */ + export function getType(this: Descriptor): DescriptorType; + /** + * Adjust the size of an open file. If this increases the file's size, the + * extra bytes are filled with zeros. + * + * Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + */ + export function setSize(this: Descriptor, size: Filesize): void; + /** + * Adjust the timestamps of an open file or directory. + * + * Note: This is similar to `futimens` in POSIX. + * + * Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + */ + export function setTimes(this: Descriptor, dataAccessTimestamp: NewTimestamp, dataModificationTimestamp: NewTimestamp): void; + /** + * Read from a descriptor, without using and updating the descriptor's offset. + * + * This function returns a list of bytes containing the data that was + * read, along with a bool which, when true, indicates that the end of the + * file was reached. The returned list will contain up to `length` bytes; it + * may return fewer than requested, if the end of the file is reached or + * if the I/O operation is interrupted. + * + * In the future, this may change to return a `stream`. + * + * Note: This is similar to `pread` in POSIX. + */ + export function read(this: Descriptor, length: Filesize, offset: Filesize): [Uint8Array | ArrayBuffer, boolean]; + /** + * Write to a descriptor, without using and updating the descriptor's offset. + * + * It is valid to write past the end of a file; the file is extended to the + * extent of the write, with bytes between the previous end and the start of + * the write set to zero. + * + * In the future, this may change to take a `stream`. + * + * Note: This is similar to `pwrite` in POSIX. + */ + export function write(this: Descriptor, buffer: Uint8Array, offset: Filesize): Filesize; + /** + * Read directory entries from a directory. + * + * On filesystems where directories contain entries referring to themselves + * and their parents, often named `.` and `..` respectively, these entries + * are omitted. + * + * This always returns a new stream which starts at the beginning of the + * directory. Multiple streams may be active on the same directory, and they + * do not interfere with each other. + */ + export function readDirectory(this: Descriptor): DirectoryEntryStream; + /** + * Synchronize the data and metadata of a file to disk. + * + * This function succeeds with no effect if the file descriptor is not + * opened for writing. + * + * Note: This is similar to `fsync` in POSIX. + */ + export function sync(this: Descriptor): void; + /** + * Create a directory. + * + * Note: This is similar to `mkdirat` in POSIX. + */ + export function createDirectoryAt(this: Descriptor, path: string): void; + /** + * Return the attributes of an open file or directory. + * + * Note: This is similar to `fstat` in POSIX. + * + * Note: This was called `fd_filestat_get` in earlier versions of WASI. + */ + export function stat(this: Descriptor): DescriptorStat; + /** + * Return the attributes of a file or directory. + * + * Note: This is similar to `fstatat` in POSIX. + * + * Note: This was called `path_filestat_get` in earlier versions of WASI. + */ + export function statAt(this: Descriptor, pathFlags: PathFlags, path: string): DescriptorStat; + /** + * Adjust the timestamps of a file or directory. + * + * Note: This is similar to `utimensat` in POSIX. + * + * Note: This was called `path_filestat_set_times` in earlier versions of + * WASI. + */ + export function setTimesAt(this: Descriptor, pathFlags: PathFlags, path: string, dataAccessTimestamp: NewTimestamp, dataModificationTimestamp: NewTimestamp): void; + /** + * Create a hard link. + * + * Note: This is similar to `linkat` in POSIX. + */ + export function linkAt(this: Descriptor, oldPathFlags: PathFlags, oldPath: string, newDescriptor: Descriptor, newPath: string): void; + /** + * Open a file or directory. + * + * The returned descriptor is not guaranteed to be the lowest-numbered + * descriptor not currently open/ it is randomized to prevent applications + * from depending on making assumptions about indexes, since this is + * error-prone in multi-threaded contexts. The returned descriptor is + * guaranteed to be less than 2**31. + * + * If `flags` contains `descriptor-flags::mutate-directory`, and the base + * descriptor doesn't have `descriptor-flags::mutate-directory` set, + * `open-at` fails with `error-code::read-only`. + * + * If `flags` contains `write` or `mutate-directory`, or `open-flags` + * contains `truncate` or `create`, and the base descriptor doesn't have + * `descriptor-flags::mutate-directory` set, `open-at` fails with + * `error-code::read-only`. + * + * Note: This is similar to `openat` in POSIX. + */ + export function openAt(this: Descriptor, pathFlags: PathFlags, path: string, openFlags: OpenFlags, flags: DescriptorFlags, modes: Modes): Descriptor; + /** + * Read the contents of a symbolic link. + * + * If the contents contain an absolute or rooted path in the underlying + * filesystem, this function fails with `error-code::not-permitted`. + * + * Note: This is similar to `readlinkat` in POSIX. + */ + export function readlinkAt(this: Descriptor, path: string): string; + /** + * Remove a directory. + * + * Return `error-code::not-empty` if the directory is not empty. + * + * Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + */ + export function removeDirectoryAt(this: Descriptor, path: string): void; + /** + * Rename a filesystem object. + * + * Note: This is similar to `renameat` in POSIX. + */ + export function renameAt(this: Descriptor, oldPath: string, newDescriptor: Descriptor, newPath: string): void; + /** + * Create a symbolic link (also known as a "symlink"). + * + * If `old-path` starts with `/`, the function fails with + * `error-code::not-permitted`. + * + * Note: This is similar to `symlinkat` in POSIX. + */ + export function symlinkAt(this: Descriptor, oldPath: string, newPath: string): void; + /** + * Check accessibility of a filesystem path. + * + * Check whether the given filesystem path names an object which is + * readable, writable, or executable, or whether it exists. + * + * This does not a guarantee that subsequent accesses will succeed, as + * filesystem permissions may be modified asynchronously by external + * entities. + * + * Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. + */ + export function accessAt(this: Descriptor, pathFlags: PathFlags, path: string, type: AccessType): void; + /** + * Unlink a filesystem object that is not a directory. + * + * Return `error-code::is-directory` if the path refers to a directory. + * Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + */ + export function unlinkFileAt(this: Descriptor, path: string): void; + /** + * Change the permissions of a filesystem object that is not a directory. + * + * Note that the ultimate meanings of these permissions is + * filesystem-specific. + * + * Note: This is similar to `fchmodat` in POSIX. + */ + export function changeFilePermissionsAt(this: Descriptor, pathFlags: PathFlags, path: string, modes: Modes): void; + /** + * Change the permissions of a directory. + * + * Note that the ultimate meanings of these permissions is + * filesystem-specific. + * + * Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" + * flag. `read` on a directory implies readability and searchability, and + * `execute` is not valid for directories. + * + * Note: This is similar to `fchmodat` in POSIX. + */ + export function changeDirectoryPermissionsAt(this: Descriptor, pathFlags: PathFlags, path: string, modes: Modes): void; + /** + * Request a shared advisory lock for an open file. + * + * This requests a *shared* lock; more than one shared lock can be held for + * a file at the same time. + * + * If the open file has an exclusive lock, this function downgrades the lock + * to a shared lock. If it has a shared lock, this function has no effect. + * + * This requests an *advisory* lock, meaning that the file could be accessed + * by other programs that don't hold the lock. + * + * It is unspecified how shared locks interact with locks acquired by + * non-WASI programs. + * + * This function blocks until the lock can be acquired. + * + * Not all filesystems support locking; on filesystems which don't support + * locking, this function returns `error-code::unsupported`. + * + * Note: This is similar to `flock(fd, LOCK_SH)` in Unix. + */ + export function lockShared(this: Descriptor): void; + /** + * Request an exclusive advisory lock for an open file. + * + * This requests an *exclusive* lock; no other locks may be held for the + * file while an exclusive lock is held. + * + * If the open file has a shared lock and there are no exclusive locks held + * for the file, this function upgrades the lock to an exclusive lock. If the + * open file already has an exclusive lock, this function has no effect. + * + * This requests an *advisory* lock, meaning that the file could be accessed + * by other programs that don't hold the lock. + * + * It is unspecified whether this function succeeds if the file descriptor + * is not opened for writing. It is unspecified how exclusive locks interact + * with locks acquired by non-WASI programs. + * + * This function blocks until the lock can be acquired. + * + * Not all filesystems support locking; on filesystems which don't support + * locking, this function returns `error-code::unsupported`. + * + * Note: This is similar to `flock(fd, LOCK_EX)` in Unix. + */ + export function lockExclusive(this: Descriptor): void; + /** + * Request a shared advisory lock for an open file. + * + * This requests a *shared* lock; more than one shared lock can be held for + * a file at the same time. + * + * If the open file has an exclusive lock, this function downgrades the lock + * to a shared lock. If it has a shared lock, this function has no effect. + * + * This requests an *advisory* lock, meaning that the file could be accessed + * by other programs that don't hold the lock. + * + * It is unspecified how shared locks interact with locks acquired by + * non-WASI programs. + * + * This function returns `error-code::would-block` if the lock cannot be + * acquired. + * + * Not all filesystems support locking; on filesystems which don't support + * locking, this function returns `error-code::unsupported`. + * + * Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. + */ + export function tryLockShared(this: Descriptor): void; + /** + * Request an exclusive advisory lock for an open file. + * + * This requests an *exclusive* lock; no other locks may be held for the + * file while an exclusive lock is held. + * + * If the open file has a shared lock and there are no exclusive locks held + * for the file, this function upgrades the lock to an exclusive lock. If the + * open file already has an exclusive lock, this function has no effect. + * + * This requests an *advisory* lock, meaning that the file could be accessed + * by other programs that don't hold the lock. + * + * It is unspecified whether this function succeeds if the file descriptor + * is not opened for writing. It is unspecified how exclusive locks interact + * with locks acquired by non-WASI programs. + * + * This function returns `error-code::would-block` if the lock cannot be + * acquired. + * + * Not all filesystems support locking; on filesystems which don't support + * locking, this function returns `error-code::unsupported`. + * + * Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. + */ + export function tryLockExclusive(this: Descriptor): void; + /** + * Release a shared or exclusive lock on an open file. + * + * Note: This is similar to `flock(fd, LOCK_UN)` in Unix. + */ + export function unlock(this: Descriptor): void; + /** + * Dispose of the specified `descriptor`, after which it may no longer + * be used. + */ + export function dropDescriptor(this: Descriptor): void; + /** + * Read a single directory entry from a `directory-entry-stream`. + */ + export function readDirectoryEntry(this: DirectoryEntryStream): DirectoryEntry | null; + /** + * Dispose of the specified `directory-entry-stream`, after which it may no longer + * be used. + */ + export function dropDirectoryEntryStream(this: DirectoryEntryStream): void; +} +import type { InputStream } from '../imports/streams'; +export { InputStream }; +import type { OutputStream } from '../imports/streams'; +export { OutputStream }; +import type { Datetime } from '../imports/wall-clock'; +export { Datetime }; +/** + * Flags determining the method of how paths are resolved. + */ +export interface PathFlags { + /** + * As long as the resolved path corresponds to a symbolic link, it is + * expanded. + */ + symlinkFollow?: boolean, +} +/** + * Open flags used by `open-at`. + */ +export interface OpenFlags { + /** + * Create file if it does not exist, similar to `O_CREAT` in POSIX. + */ + create?: boolean, + /** + * Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + */ + directory?: boolean, + /** + * Fail if file already exists, similar to `O_EXCL` in POSIX. + */ + exclusive?: boolean, + /** + * Truncate file to size 0, similar to `O_TRUNC` in POSIX. + */ + truncate?: boolean, +} +/** + * Permissions mode used by `open-at`, `change-file-permissions-at`, and + * similar. + */ +export interface Modes { + /** + * True if the resource is considered readable by the containing + * filesystem. + */ + readable?: boolean, + /** + * True if the resource is considered writable by the containing + * filesystem. + */ + writable?: boolean, + /** + * True if the resource is considered executable by the containing + * filesystem. This does not apply to directories. + */ + executable?: boolean, +} +/** + * Number of hard links to an inode. + */ +export type LinkCount = bigint; +/** + * Filesystem object serial number that is unique within its file system. + */ +export type Inode = bigint; +/** + * File size or length of a region within a file. + */ +export type Filesize = bigint; +/** + * Error codes returned by functions, similar to `errno` in POSIX. + * Not all of these error codes are returned by the functions provided by this + * API; some are used in higher-level library layers, and others are provided + * merely for alignment with POSIX. + * + * # Variants + * + * ## `"access"` + * + * Permission denied, similar to `EACCES` in POSIX. + * + * ## `"would-block"` + * + * Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + * + * ## `"already"` + * + * Connection already in progress, similar to `EALREADY` in POSIX. + * + * ## `"bad-descriptor"` + * + * Bad descriptor, similar to `EBADF` in POSIX. + * + * ## `"busy"` + * + * Device or resource busy, similar to `EBUSY` in POSIX. + * + * ## `"deadlock"` + * + * Resource deadlock would occur, similar to `EDEADLK` in POSIX. + * + * ## `"quota"` + * + * Storage quota exceeded, similar to `EDQUOT` in POSIX. + * + * ## `"exist"` + * + * File exists, similar to `EEXIST` in POSIX. + * + * ## `"file-too-large"` + * + * File too large, similar to `EFBIG` in POSIX. + * + * ## `"illegal-byte-sequence"` + * + * Illegal byte sequence, similar to `EILSEQ` in POSIX. + * + * ## `"in-progress"` + * + * Operation in progress, similar to `EINPROGRESS` in POSIX. + * + * ## `"interrupted"` + * + * Interrupted function, similar to `EINTR` in POSIX. + * + * ## `"invalid"` + * + * Invalid argument, similar to `EINVAL` in POSIX. + * + * ## `"io"` + * + * I/O error, similar to `EIO` in POSIX. + * + * ## `"is-directory"` + * + * Is a directory, similar to `EISDIR` in POSIX. + * + * ## `"loop"` + * + * Too many levels of symbolic links, similar to `ELOOP` in POSIX. + * + * ## `"too-many-links"` + * + * Too many links, similar to `EMLINK` in POSIX. + * + * ## `"message-size"` + * + * Message too large, similar to `EMSGSIZE` in POSIX. + * + * ## `"name-too-long"` + * + * Filename too long, similar to `ENAMETOOLONG` in POSIX. + * + * ## `"no-device"` + * + * No such device, similar to `ENODEV` in POSIX. + * + * ## `"no-entry"` + * + * No such file or directory, similar to `ENOENT` in POSIX. + * + * ## `"no-lock"` + * + * No locks available, similar to `ENOLCK` in POSIX. + * + * ## `"insufficient-memory"` + * + * Not enough space, similar to `ENOMEM` in POSIX. + * + * ## `"insufficient-space"` + * + * No space left on device, similar to `ENOSPC` in POSIX. + * + * ## `"not-directory"` + * + * Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + * + * ## `"not-empty"` + * + * Directory not empty, similar to `ENOTEMPTY` in POSIX. + * + * ## `"not-recoverable"` + * + * State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + * + * ## `"unsupported"` + * + * Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + * + * ## `"no-tty"` + * + * Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + * + * ## `"no-such-device"` + * + * No such device or address, similar to `ENXIO` in POSIX. + * + * ## `"overflow"` + * + * Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + * + * ## `"not-permitted"` + * + * Operation not permitted, similar to `EPERM` in POSIX. + * + * ## `"pipe"` + * + * Broken pipe, similar to `EPIPE` in POSIX. + * + * ## `"read-only"` + * + * Read-only file system, similar to `EROFS` in POSIX. + * + * ## `"invalid-seek"` + * + * Invalid seek, similar to `ESPIPE` in POSIX. + * + * ## `"text-file-busy"` + * + * Text file busy, similar to `ETXTBSY` in POSIX. + * + * ## `"cross-device"` + * + * Cross-device link, similar to `EXDEV` in POSIX. + */ +export type ErrorCode = 'access' | 'would-block' | 'already' | 'bad-descriptor' | 'busy' | 'deadlock' | 'quota' | 'exist' | 'file-too-large' | 'illegal-byte-sequence' | 'in-progress' | 'interrupted' | 'invalid' | 'io' | 'is-directory' | 'loop' | 'too-many-links' | 'message-size' | 'name-too-long' | 'no-device' | 'no-entry' | 'no-lock' | 'insufficient-memory' | 'insufficient-space' | 'not-directory' | 'not-empty' | 'not-recoverable' | 'unsupported' | 'no-tty' | 'no-such-device' | 'overflow' | 'not-permitted' | 'pipe' | 'read-only' | 'invalid-seek' | 'text-file-busy' | 'cross-device'; +/** + * A stream of directory entries. + * + * This [represents a stream of `dir-entry`](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Streams). + */ +export type DirectoryEntryStream = number; +/** + * Identifier for a device containing a file system. Can be used in + * combination with `inode` to uniquely identify a file or directory in + * the filesystem. + */ +export type Device = bigint; +/** + * The type of a filesystem object referenced by a descriptor. + * + * Note: This was called `filetype` in earlier versions of WASI. + * + * # Variants + * + * ## `"unknown"` + * + * The type of the descriptor or file is unknown or is different from + * any of the other types specified. + * + * ## `"block-device"` + * + * The descriptor refers to a block device inode. + * + * ## `"character-device"` + * + * The descriptor refers to a character device inode. + * + * ## `"directory"` + * + * The descriptor refers to a directory inode. + * + * ## `"fifo"` + * + * The descriptor refers to a named pipe. + * + * ## `"symbolic-link"` + * + * The file refers to a symbolic link inode. + * + * ## `"regular-file"` + * + * The descriptor refers to a regular file inode. + * + * ## `"socket"` + * + * The descriptor refers to a socket. + */ +export type DescriptorType = 'unknown' | 'block-device' | 'character-device' | 'directory' | 'fifo' | 'symbolic-link' | 'regular-file' | 'socket'; +/** + * A directory entry. + */ +export interface DirectoryEntry { + /** + * The serial number of the object referred to by this directory entry. + * May be none if the inode value is not known. + * + * When this is none, libc implementations might do an extra `stat-at` + * call to retrieve the inode number to fill their `d_ino` fields, so + * implementations which can set this to a non-none value should do so. + */ + inode?: Inode, + /** + * The type of the file referred to by this directory entry. + */ + type: DescriptorType, + /** + * The name of the object. + */ + name: string, +} +/** + * Descriptor flags. + * + * Note: This was called `fdflags` in earlier versions of WASI. + */ +export interface DescriptorFlags { + /** + * Read mode: Data can be read. + */ + read?: boolean, + /** + * Write mode: Data can be written to. + */ + write?: boolean, + /** + * Request that writes be performed according to synchronized I/O file + * integrity completion. The data stored in the file and the file's + * metadata are synchronized. This is similar to `O_SYNC` in POSIX. + * + * The precise semantics of this operation have not yet been defined for + * WASI. At this time, it should be interpreted as a request, and not a + * requirement. + */ + fileIntegritySync?: boolean, + /** + * Request that writes be performed according to synchronized I/O data + * integrity completion. Only the data stored in the file is + * synchronized. This is similar to `O_DSYNC` in POSIX. + * + * The precise semantics of this operation have not yet been defined for + * WASI. At this time, it should be interpreted as a request, and not a + * requirement. + */ + dataIntegritySync?: boolean, + /** + * Requests that reads be performed at the same level of integrety + * requested for writes. This is similar to `O_RSYNC` in POSIX. + * + * The precise semantics of this operation have not yet been defined for + * WASI. At this time, it should be interpreted as a request, and not a + * requirement. + */ + requestedWriteSync?: boolean, + /** + * Mutating directories mode: Directory contents may be mutated. + * + * When this flag is unset on a descriptor, operations using the + * descriptor which would create, rename, delete, modify the data or + * metadata of filesystem objects, or obtain another handle which + * would permit any of those, shall fail with `error-code::read-only` if + * they would otherwise succeed. + * + * This may only be set on directories. + */ + mutateDirectory?: boolean, +} +/** + * A descriptor is a reference to a filesystem object, which may be a file, + * directory, named pipe, special file, or other object on which filesystem + * calls may be made. + * + * This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + */ +export type Descriptor = number; +/** + * When setting a timestamp, this gives the value to set it to. + */ +export type NewTimestamp = NewTimestampNoChange | NewTimestampNow | NewTimestampTimestamp; +/** + * Leave the timestamp set to its previous value. + */ +export interface NewTimestampNoChange { + tag: 'no-change', +} +/** + * Set the timestamp to the current time of the system clock associated + * with the filesystem. + */ +export interface NewTimestampNow { + tag: 'now', +} +/** + * Set the timestamp to the given value. + */ +export interface NewTimestampTimestamp { + tag: 'timestamp', + val: Datetime, +} +/** + * File attributes. + * + * Note: This was called `filestat` in earlier versions of WASI. + */ +export interface DescriptorStat { + /** + * Device ID of device containing the file. + */ + device: Device, + /** + * File serial number. + */ + inode: Inode, + /** + * File type. + */ + type: DescriptorType, + /** + * Number of hard links to the file. + */ + linkCount: LinkCount, + /** + * For regular files, the file size in bytes. For symbolic links, the + * length in bytes of the pathname contained in the symbolic link. + */ + size: Filesize, + /** + * Last data access timestamp. + */ + dataAccessTimestamp: Datetime, + /** + * Last data modification timestamp. + */ + dataModificationTimestamp: Datetime, + /** + * Last file status change timestamp. + */ + statusChangeTimestamp: Datetime, +} +/** + * File or memory access pattern advisory information. + * + * # Variants + * + * ## `"normal"` + * + * The application has no advice to give on its behavior with respect + * to the specified data. + * + * ## `"sequential"` + * + * The application expects to access the specified data sequentially + * from lower offsets to higher offsets. + * + * ## `"random"` + * + * The application expects to access the specified data in a random + * order. + * + * ## `"will-need"` + * + * The application expects to access the specified data in the near + * future. + * + * ## `"dont-need"` + * + * The application expects that it will not access the specified data + * in the near future. + * + * ## `"no-reuse"` + * + * The application expects to access the specified data once and then + * not reuse it thereafter. + */ +export type Advice = 'normal' | 'sequential' | 'random' | 'will-need' | 'dont-need' | 'no-reuse'; +/** + * Access type used by `access-at`. + */ +export type AccessType = AccessTypeAccess | AccessTypeExists; +/** + * Test for readability, writeability, or executability. + */ +export interface AccessTypeAccess { + tag: 'access', + val: Modes, +} +/** + * Test whether the path exists. + */ +export interface AccessTypeExists { + tag: 'exists', +} diff --git a/packages/preview2-shim/types/imports/filesystem.d.ts b/packages/preview2-shim/types/imports/filesystem.d.ts deleted file mode 100644 index 5c3c2d14f..000000000 --- a/packages/preview2-shim/types/imports/filesystem.d.ts +++ /dev/null @@ -1,210 +0,0 @@ -export namespace Filesystem { - export function readViaStream(this: Descriptor, offset: Filesize): InputStream; - export function writeViaStream(this: Descriptor, offset: Filesize): OutputStream; - export function appendViaStream(this: Descriptor): OutputStream; - export function advise(this: Descriptor, offset: Filesize, length: Filesize, advice: Advice): void; - export function syncData(this: Descriptor): void; - export function getFlags(this: Descriptor): DescriptorFlags; - export function getType(this: Descriptor): DescriptorType; - export function setSize(this: Descriptor, size: Filesize): void; - export function setTimes(this: Descriptor, dataAccessTimestamp: NewTimestamp, dataModificationTimestamp: NewTimestamp): void; - export function read(this: Descriptor, length: Filesize, offset: Filesize): [Uint8Array | ArrayBuffer, boolean]; - export function write(this: Descriptor, buffer: Uint8Array, offset: Filesize): Filesize; - export function readDirectory(this: Descriptor): DirectoryEntryStream; - export function sync(this: Descriptor): void; - export function createDirectoryAt(this: Descriptor, path: string): void; - export function stat(this: Descriptor): DescriptorStat; - export function statAt(this: Descriptor, pathFlags: PathFlags, path: string): DescriptorStat; - export function setTimesAt(this: Descriptor, pathFlags: PathFlags, path: string, dataAccessTimestamp: NewTimestamp, dataModificationTimestamp: NewTimestamp): void; - export function linkAt(this: Descriptor, oldPathFlags: PathFlags, oldPath: string, newDescriptor: Descriptor, newPath: string): void; - export function openAt(this: Descriptor, pathFlags: PathFlags, path: string, openFlags: OpenFlags, flags: DescriptorFlags, modes: Modes): Descriptor; - export function readlinkAt(this: Descriptor, path: string): string; - export function removeDirectoryAt(this: Descriptor, path: string): void; - export function renameAt(this: Descriptor, oldPath: string, newDescriptor: Descriptor, newPath: string): void; - export function symlinkAt(this: Descriptor, oldPath: string, newPath: string): void; - export function unlinkFileAt(this: Descriptor, path: string): void; - export function changeFilePermissionsAt(this: Descriptor, pathFlags: PathFlags, path: string, modes: Modes): void; - export function changeDirectoryPermissionsAt(this: Descriptor, pathFlags: PathFlags, path: string, modes: Modes): void; - export function lockShared(this: Descriptor): void; - export function lockExclusive(this: Descriptor): void; - export function tryLockShared(this: Descriptor): void; - export function tryLockExclusive(this: Descriptor): void; - export function unlock(this: Descriptor): void; - export function dropDescriptor(this: Descriptor): void; - export function readDirectoryEntry(this: DirectoryEntryStream): DirectoryEntry | null; - export function dropDirectoryEntryStream(this: DirectoryEntryStream): void; -} -export type Descriptor = number; -export type Filesize = bigint; -import type { InputStream } from '../imports/streams'; -export { InputStream }; -import type { OutputStream } from '../imports/streams'; -export { OutputStream }; -/** - * # Variants - * - * ## `"normal"` - * - * ## `"sequential"` - * - * ## `"random"` - * - * ## `"will-need"` - * - * ## `"dont-need"` - * - * ## `"no-reuse"` - */ -export type Advice = 'normal' | 'sequential' | 'random' | 'will-need' | 'dont-need' | 'no-reuse'; -/** - * # Variants - * - * ## `"access"` - * - * ## `"would-block"` - * - * ## `"already"` - * - * ## `"bad-descriptor"` - * - * ## `"busy"` - * - * ## `"deadlock"` - * - * ## `"quota"` - * - * ## `"exist"` - * - * ## `"file-too-large"` - * - * ## `"illegal-byte-sequence"` - * - * ## `"in-progress"` - * - * ## `"interrupted"` - * - * ## `"invalid"` - * - * ## `"io"` - * - * ## `"is-directory"` - * - * ## `"loop"` - * - * ## `"too-many-links"` - * - * ## `"message-size"` - * - * ## `"name-too-long"` - * - * ## `"no-device"` - * - * ## `"no-entry"` - * - * ## `"no-lock"` - * - * ## `"insufficient-memory"` - * - * ## `"insufficient-space"` - * - * ## `"not-directory"` - * - * ## `"not-empty"` - * - * ## `"not-recoverable"` - * - * ## `"unsupported"` - * - * ## `"no-tty"` - * - * ## `"no-such-device"` - * - * ## `"overflow"` - * - * ## `"not-permitted"` - * - * ## `"pipe"` - * - * ## `"read-only"` - * - * ## `"invalid-seek"` - * - * ## `"text-file-busy"` - * - * ## `"cross-device"` - */ -export type ErrorCode = 'access' | 'would-block' | 'already' | 'bad-descriptor' | 'busy' | 'deadlock' | 'quota' | 'exist' | 'file-too-large' | 'illegal-byte-sequence' | 'in-progress' | 'interrupted' | 'invalid' | 'io' | 'is-directory' | 'loop' | 'too-many-links' | 'message-size' | 'name-too-long' | 'no-device' | 'no-entry' | 'no-lock' | 'insufficient-memory' | 'insufficient-space' | 'not-directory' | 'not-empty' | 'not-recoverable' | 'unsupported' | 'no-tty' | 'no-such-device' | 'overflow' | 'not-permitted' | 'pipe' | 'read-only' | 'invalid-seek' | 'text-file-busy' | 'cross-device'; -export interface DescriptorFlags { - read?: boolean, - write?: boolean, - fileIntegritySync?: boolean, - dataIntegritySync?: boolean, - requestedWriteSync?: boolean, - mutateDirectory?: boolean, -} -/** - * # Variants - * - * ## `"unknown"` - * - * ## `"block-device"` - * - * ## `"character-device"` - * - * ## `"directory"` - * - * ## `"fifo"` - * - * ## `"symbolic-link"` - * - * ## `"regular-file"` - * - * ## `"socket"` - */ -export type DescriptorType = 'unknown' | 'block-device' | 'character-device' | 'directory' | 'fifo' | 'symbolic-link' | 'regular-file' | 'socket'; -import type { Datetime } from '../imports/wall-clock'; -export { Datetime }; -export type NewTimestamp = NewTimestampNoChange | NewTimestampNow | NewTimestampTimestamp; -export interface NewTimestampNoChange { - tag: 'no-change', -} -export interface NewTimestampNow { - tag: 'now', -} -export interface NewTimestampTimestamp { - tag: 'timestamp', - val: Datetime, -} -export type DirectoryEntryStream = number; -export type Device = bigint; -export type Inode = bigint; -export type LinkCount = bigint; -export interface DescriptorStat { - device: Device, - inode: Inode, - type: DescriptorType, - linkCount: LinkCount, - size: Filesize, - dataAccessTimestamp: Datetime, - dataModificationTimestamp: Datetime, - statusChangeTimestamp: Datetime, -} -export interface PathFlags { - symlinkFollow?: boolean, -} -export interface OpenFlags { - create?: boolean, - directory?: boolean, - exclusive?: boolean, - truncate?: boolean, -} -export interface Modes { - readable?: boolean, - writeable?: boolean, - executable?: boolean, -} -export interface DirectoryEntry { - inode?: Inode, - type: DescriptorType, - name: string, -} diff --git a/packages/preview2-shim/types/imports/default-outgoing-HTTP.d.ts b/packages/preview2-shim/types/imports/http-outgoing-handler.d.ts similarity index 90% rename from packages/preview2-shim/types/imports/default-outgoing-HTTP.d.ts rename to packages/preview2-shim/types/imports/http-outgoing-handler.d.ts index 8cedf49ee..54c7bd013 100644 --- a/packages/preview2-shim/types/imports/default-outgoing-HTTP.d.ts +++ b/packages/preview2-shim/types/imports/http-outgoing-handler.d.ts @@ -1,4 +1,4 @@ -export namespace DefaultOutgoingHttp { +export namespace HttpOutgoingHandler { export function handle(request: OutgoingRequest, options: RequestOptions | null): FutureIncomingResponse; } import type { OutgoingRequest } from '../imports/types'; diff --git a/packages/preview2-shim/types/imports/types.d.ts b/packages/preview2-shim/types/imports/http-types.d.ts similarity index 99% rename from packages/preview2-shim/types/imports/types.d.ts rename to packages/preview2-shim/types/imports/http-types.d.ts index c9a292570..52d189c6c 100644 --- a/packages/preview2-shim/types/imports/types.d.ts +++ b/packages/preview2-shim/types/imports/http-types.d.ts @@ -1,4 +1,4 @@ -export namespace Types { +export namespace HttpTypes { export function dropFields(fields: Fields): void; export function newFields(entries: [string, string][]): Fields; export function fieldsGet(fields: Fields, name: string): string[]; @@ -33,15 +33,32 @@ export namespace Types { export function futureIncomingResponseGet(f: FutureIncomingResponse): Result | null; export function listenToFutureIncomingResponse(f: FutureIncomingResponse): Pollable; } -export type Fields = number; import type { InputStream } from '../imports/streams'; export { InputStream }; -export type IncomingStream = InputStream; -export type Trailers = Fields; import type { OutputStream } from '../imports/streams'; export { OutputStream }; +import type { Pollable } from '../imports/poll'; +export { Pollable }; +export type StatusCode = number; +export type Scheme = SchemeHttp | SchemeHttps | SchemeOther; +export interface SchemeHttp { + tag: 'HTTP', +} +export interface SchemeHttps { + tag: 'HTTPS', +} +export interface SchemeOther { + tag: 'other', + val: string, +} +export type ResponseOutparam = number; +export interface RequestOptions { + connectTimeoutMs?: number, + firstByteTimeoutMs?: number, + betweenBytesTimeoutMs?: number, +} export type OutgoingStream = OutputStream; -export type IncomingRequest = number; +export type OutgoingResponse = number; export type OutgoingRequest = number; export type Method = MethodGet | MethodHead | MethodPost | MethodPut | MethodDelete | MethodConnect | MethodOptions | MethodTrace | MethodPatch | MethodOther; export interface MethodGet { @@ -75,20 +92,13 @@ export interface MethodOther { tag: 'other', val: string, } -export type Scheme = SchemeHttp | SchemeHttps | SchemeOther; -export interface SchemeHttp { - tag: 'HTTP', -} -export interface SchemeHttps { - tag: 'HTTPS', -} -export interface SchemeOther { - tag: 'other', - val: string, -} +export type IncomingStream = InputStream; +export type IncomingResponse = number; +export type IncomingRequest = number; +export type FutureIncomingResponse = number; +export type Fields = number; +export type Trailers = Fields; export type Headers = Fields; -export type ResponseOutparam = number; -export type OutgoingResponse = number; export type Error = ErrorInvalidUrl | ErrorTimeoutError | ErrorProtocolError | ErrorUnexpectedError; export interface ErrorInvalidUrl { tag: 'invalid-url', @@ -106,14 +116,4 @@ export interface ErrorUnexpectedError { tag: 'unexpected-error', val: string, } -export type IncomingResponse = number; -export type StatusCode = number; -export type FutureIncomingResponse = number; -import type { Pollable } from '../imports/poll'; -export { Pollable }; -export interface RequestOptions { - connectTimeoutMs?: number, - firstByteTimeoutMs?: number, - betweenBytesTimeoutMs?: number, -} export type Result = { tag: 'ok', val: T } | { tag: 'err', val: E }; diff --git a/packages/preview2-shim/types/imports/io-streams.d.ts b/packages/preview2-shim/types/imports/io-streams.d.ts new file mode 100644 index 000000000..dd05fd8ec --- /dev/null +++ b/packages/preview2-shim/types/imports/io-streams.d.ts @@ -0,0 +1,180 @@ +export namespace IoStreams { + /** + * Read bytes from a stream. + * + * This function returns a list of bytes containing the data that was + * read, along with a bool which, when true, indicates that the end of the + * stream was reached. The returned list will contain up to `len` bytes; it + * may return fewer than requested, but not more. + * + * Once a stream has reached the end, subsequent calls to read or + * `skip` will always report end-of-stream rather than producing more + * data. + * + * If `len` is 0, it represents a request to read 0 bytes, which should + * always succeed, assuming the stream hasn't reached its end yet, and + * return an empty list. + * + * The len here is a `u64`, but some callees may not be able to allocate + * a buffer as large as that would imply. + * FIXME: describe what happens if allocation fails. + */ + export function read(this: InputStream, len: bigint): [Uint8Array | ArrayBuffer, boolean]; + /** + * Read bytes from a stream, with blocking. + * + * This is similar to `read`, except that it blocks until at least one + * byte can be read. + */ + export function blockingRead(this: InputStream, len: bigint): [Uint8Array | ArrayBuffer, boolean]; + /** + * Skip bytes from a stream. + * + * This is similar to the `read` function, but avoids copying the + * bytes into the instance. + * + * Once a stream has reached the end, subsequent calls to read or + * `skip` will always report end-of-stream rather than producing more + * data. + * + * This function returns the number of bytes skipped, along with a bool + * indicating whether the end of the stream was reached. The returned + * value will be at most `len`; it may be less. + */ + export function skip(this: InputStream, len: bigint): [bigint, boolean]; + /** + * Skip bytes from a stream, with blocking. + * + * This is similar to `skip`, except that it blocks until at least one + * byte can be consumed. + */ + export function blockingSkip(this: InputStream, len: bigint): [bigint, boolean]; + /** + * Create a `pollable` which will resolve once either the specified stream + * has bytes available to read or the other end of the stream has been + * closed. + */ + export function subscribeToInputStream(this: InputStream): Pollable; + /** + * Dispose of the specified `input-stream`, after which it may no longer + * be used. + */ + export function dropInputStream(this: InputStream): void; + /** + * Write bytes to a stream. + * + * This function returns a `u64` indicating the number of bytes from + * `buf` that were written; it may be less than the full list. + */ + export function write(this: OutputStream, buf: Uint8Array): bigint; + /** + * Write bytes to a stream, with blocking. + * + * This is similar to `write`, except that it blocks until at least one + * byte can be written. + */ + export function blockingWrite(this: OutputStream, buf: Uint8Array): bigint; + /** + * Write multiple zero bytes to a stream. + * + * This function returns a `u64` indicating the number of zero bytes + * that were written; it may be less than `len`. + */ + export function writeZeroes(this: OutputStream, len: bigint): bigint; + /** + * Write multiple zero bytes to a stream, with blocking. + * + * This is similar to `write-zeroes`, except that it blocks until at least + * one byte can be written. + */ + export function blockingWriteZeroes(this: OutputStream, len: bigint): bigint; + /** + * Read from one stream and write to another. + * + * This function returns the number of bytes transferred; it may be less + * than `len`. + * + * Unlike other I/O functions, this function blocks until all the data + * read from the input stream has been written to the output stream. + */ + export function splice(this: OutputStream, src: InputStream, len: bigint): [bigint, boolean]; + /** + * Read from one stream and write to another, with blocking. + * + * This is similar to `splice`, except that it blocks until at least + * one byte can be read. + */ + export function blockingSplice(this: OutputStream, src: InputStream, len: bigint): [bigint, boolean]; + /** + * Forward the entire contents of an input stream to an output stream. + * + * This function repeatedly reads from the input stream and writes + * the data to the output stream, until the end of the input stream + * is reached, or an error is encountered. + * + * Unlike other I/O functions, this function blocks until the end + * of the input stream is seen and all the data has been written to + * the output stream. + * + * This function returns the number of bytes transferred. + */ + export function forward(this: OutputStream, src: InputStream): bigint; + /** + * Create a `pollable` which will resolve once either the specified stream + * is ready to accept bytes or the other end of the stream has been closed. + */ + export function subscribeToOutputStream(this: OutputStream): Pollable; + /** + * Dispose of the specified `output-stream`, after which it may no longer + * be used. + */ + export function dropOutputStream(this: OutputStream): void; +} +import type { Pollable } from '../imports/poll'; +export { Pollable }; +/** + * An error type returned from a stream operation. Currently this + * doesn't provide any additional information. + */ +export interface StreamError { +} +/** + * An output bytestream. In the future, this will be replaced by handle + * types. + * + * This conceptually represents a `stream`. It's temporary + * scaffolding until component-model's async features are ready. + * + * `output-stream`s are *non-blocking* to the extent practical on + * underlying platforms. Except where specified otherwise, I/O operations also + * always return promptly, after the number of bytes that can be written + * promptly, which could even be zero. To wait for the stream to be ready to + * accept data, the `subscribe-to-output-stream` function to obtain a + * `pollable` which can be polled for using `wasi_poll`. + * + * And at present, it is a `u32` instead of being an actual handle, until + * the wit-bindgen implementation of handles and resources is ready. + * + * This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + */ +export type OutputStream = number; +/** + * An input bytestream. In the future, this will be replaced by handle + * types. + * + * This conceptually represents a `stream`. It's temporary + * scaffolding until component-model's async features are ready. + * + * `input-stream`s are *non-blocking* to the extent practical on underlying + * platforms. I/O operations always return promptly; if fewer bytes are + * promptly available than requested, they return the number of bytes promptly + * available, which could even be zero. To wait for data to be available, + * use the `subscribe-to-input-stream` function to obtain a `pollable` which + * can be polled for using `wasi_poll`. + * + * And at present, it is a `u32` instead of being an actual handle, until + * the wit-bindgen implementation of handles and resources is ready. + * + * This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + */ +export type InputStream = number; diff --git a/packages/preview2-shim/types/imports/ip-name-lookup.d.ts b/packages/preview2-shim/types/imports/ip-name-lookup.d.ts deleted file mode 100644 index 8f486982a..000000000 --- a/packages/preview2-shim/types/imports/ip-name-lookup.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -export namespace IpNameLookup { - export function resolveAddresses(network: Network, name: string, addressFamily: IpAddressFamily | null, includeUnavailable: boolean): ResolveAddressStream; - export function resolveNextAddress(this: ResolveAddressStream): IpAddress | null; - export function dropResolveAddressStream(this: ResolveAddressStream): void; - export function nonBlocking(this: ResolveAddressStream): boolean; - export function setNonBlocking(this: ResolveAddressStream, value: boolean): void; - export function subscribe(this: ResolveAddressStream): Pollable; -} -import type { Network } from '../imports/network'; -export { Network }; -import type { IpAddressFamily } from '../imports/network'; -export { IpAddressFamily }; -export type ResolveAddressStream = number; -import type { Error } from '../imports/network'; -export { Error }; -import type { IpAddress } from '../imports/network'; -export { IpAddress }; -import type { Pollable } from '../imports/poll'; -export { Pollable }; diff --git a/packages/preview2-shim/types/imports/logging-handler.d.ts b/packages/preview2-shim/types/imports/logging-handler.d.ts new file mode 100644 index 000000000..7052f0dac --- /dev/null +++ b/packages/preview2-shim/types/imports/logging-handler.d.ts @@ -0,0 +1,40 @@ +export namespace LoggingHandler { + /** + * Emit a log message. + * + * A log message has a `level` describing what kind of message is being + * sent, a context, which is an uninterpreted string meant to help + * consumers group similar messages, and a string containing the message + * text. + */ + export function log(level: Level, context: string, message: string): void; +} +/** + * A log level, describing a kind of message. + * + * # Variants + * + * ## `"trace"` + * + * Describes messages about the values of variables and the flow of + * control within a program. + * + * ## `"debug"` + * + * Describes messages likely to be of interest to someone debugging a + * program. + * + * ## `"info"` + * + * Describes messages likely to be of interest to someone monitoring a + * program. + * + * ## `"warn"` + * + * Describes messages indicating hazardous situations. + * + * ## `"error"` + * + * Describes messages indicating serious errors. + */ +export type Level = 'trace' | 'debug' | 'info' | 'warn' | 'error'; diff --git a/packages/preview2-shim/types/imports/monotonic-clock.d.ts b/packages/preview2-shim/types/imports/monotonic-clock.d.ts deleted file mode 100644 index 13877f078..000000000 --- a/packages/preview2-shim/types/imports/monotonic-clock.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export namespace MonotonicClock { - export function now(): Instant; - export function resolution(): Instant; - export function subscribe(when: Instant, absolute: boolean): Pollable; -} -export type Instant = bigint; -import type { Pollable } from '../imports/poll'; -export { Pollable }; diff --git a/packages/preview2-shim/types/imports/network.d.ts b/packages/preview2-shim/types/imports/network.d.ts deleted file mode 100644 index c7e63d259..000000000 --- a/packages/preview2-shim/types/imports/network.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -export namespace Network { - export function dropNetwork(this: Network): void; -} -export type Network = number; -export type Ipv6Address = [number, number, number, number, number, number, number, number]; -export interface Ipv6SocketAddress { - port: number, - flowInfo: number, - address: Ipv6Address, - scopeId: number, -} -export type Ipv4Address = [number, number, number, number]; -export interface Ipv4SocketAddress { - port: number, - address: Ipv4Address, -} -export type IpSocketAddress = IpSocketAddressIpv4 | IpSocketAddressIpv6; -export interface IpSocketAddressIpv4 { - tag: 'ipv4', - val: Ipv4SocketAddress, -} -export interface IpSocketAddressIpv6 { - tag: 'ipv6', - val: Ipv6SocketAddress, -} -export type IpAddress = IpAddressIpv4 | IpAddressIpv6; -export interface IpAddressIpv4 { - tag: 'ipv4', - val: Ipv4Address, -} -export interface IpAddressIpv6 { - tag: 'ipv6', - val: Ipv6Address, -} -/** - * # Variants - * - * ## `"unknown"` - * - * ## `"again"` - */ -export type Error = 'unknown' | 'again'; -/** - * # Variants - * - * ## `"ipv4"` - * - * ## `"ipv6"` - */ -export type IpAddressFamily = 'ipv4' | 'ipv6'; diff --git a/packages/preview2-shim/types/imports/poll-poll.d.ts b/packages/preview2-shim/types/imports/poll-poll.d.ts new file mode 100644 index 000000000..ff65dab76 --- /dev/null +++ b/packages/preview2-shim/types/imports/poll-poll.d.ts @@ -0,0 +1,41 @@ +export namespace PollPoll { + /** + * Dispose of the specified `pollable`, after which it may no longer + * be used. + */ + export function dropPollable(this: Pollable): void; + /** + * Poll for completion on a set of pollables. + * + * The "oneoff" in the name refers to the fact that this function must do a + * linear scan through the entire list of subscriptions, which may be + * inefficient if the number is large and the same subscriptions are used + * many times. In the future, this is expected to be obsoleted by the + * component model async proposal, which will include a scalable waiting + * facility. + * + * Note that the return type would ideally be `list`, but that would + * be more difficult to polyfill given the current state of `wit-bindgen`. + * See + * for details. For now, we use zero to mean "not ready" and non-zero to + * mean "ready". + */ + export function pollOneoff(in: Uint32Array): Uint8Array | ArrayBuffer; +} +/** + * A "pollable" handle. + * + * This is conceptually represents a `stream<_, _>`, or in other words, + * a stream that one can wait on, repeatedly, but which does not itself + * produce any data. It's temporary scaffolding until component-model's + * async features are ready. + * + * And at present, it is a `u32` instead of being an actual handle, until + * the wit-bindgen implementation of handles and resources is ready. + * + * `pollable` lifetimes are not automatically managed. Users must ensure + * that they do not outlive the resource they reference. + * + * This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + */ +export type Pollable = number; diff --git a/packages/preview2-shim/types/imports/poll.d.ts b/packages/preview2-shim/types/imports/poll.d.ts deleted file mode 100644 index 4182efe5b..000000000 --- a/packages/preview2-shim/types/imports/poll.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export namespace Poll { - export function dropPollable(this: Pollable): void; - export function pollOneoff(in_: Uint32Array): Uint8Array | ArrayBuffer; -} -export type Pollable = number; diff --git a/packages/preview2-shim/types/imports/random-insecure-seed.d.ts b/packages/preview2-shim/types/imports/random-insecure-seed.d.ts new file mode 100644 index 000000000..a5673336f --- /dev/null +++ b/packages/preview2-shim/types/imports/random-insecure-seed.d.ts @@ -0,0 +1,22 @@ +export namespace RandomInsecureSeed { + /** + * Return a 128-bit value that may contain a pseudo-random value. + * + * The returned value is not required to be computed from a CSPRNG, and may + * even be entirely deterministic. Host implementations are encouraged to + * provide pseudo-random values to any program exposed to + * attacker-controlled content, to enable DoS protection built into many + * languages' hash-map implementations. + * + * This function is intended to only be called once, by a source language + * to initialize Denial Of Service (DoS) protection in its hash-map + * implementation. + * + * # Expected future evolution + * + * This will likely be changed to a value import, to prevent it from being + * called multiple times and potentially used for purposes other than DoS + * protection. + */ + export function insecureSeed(): [bigint, bigint]; +} diff --git a/packages/preview2-shim/types/imports/random-insecure.d.ts b/packages/preview2-shim/types/imports/random-insecure.d.ts new file mode 100644 index 000000000..05911a7d1 --- /dev/null +++ b/packages/preview2-shim/types/imports/random-insecure.d.ts @@ -0,0 +1,20 @@ +export namespace RandomInsecure { + /** + * Return `len` insecure pseudo-random bytes. + * + * This function is not cryptographically secure. Do not use it for + * anything related to security. + * + * There are no requirements on the values of the returned bytes, however + * implementations are encouraged to return evenly distributed values with + * a long period. + */ + export function getInsecureRandomBytes(len: bigint): Uint8Array | ArrayBuffer; + /** + * Return an insecure pseudo-random `u64` value. + * + * This function returns the same type of pseudo-random data as + * `get-insecure-random-bytes`, represented as a `u64`. + */ + export function getInsecureRandomU64(): bigint; +} diff --git a/packages/preview2-shim/types/imports/random-random.d.ts b/packages/preview2-shim/types/imports/random-random.d.ts new file mode 100644 index 000000000..6fcc78545 --- /dev/null +++ b/packages/preview2-shim/types/imports/random-random.d.ts @@ -0,0 +1,22 @@ +export namespace RandomRandom { + /** + * Return `len` cryptographically-secure pseudo-random bytes. + * + * This function must produce data from an adequately seeded + * cryptographically-secure pseudo-random number generator (CSPRNG), so it + * must not block, from the perspective of the calling program, and the + * returned data is always unpredictable. + * + * This function must always return fresh pseudo-random data. Deterministic + * environments must omit this function, rather than implementing it with + * deterministic data. + */ + export function getRandomBytes(len: bigint): Uint8Array | ArrayBuffer; + /** + * Return a cryptographically-secure pseudo-random `u64` value. + * + * This function returns the same type of pseudo-random data as + * `get-random-bytes`, represented as a `u64`. + */ + export function getRandomU64(): bigint; +} diff --git a/packages/preview2-shim/types/imports/random.d.ts b/packages/preview2-shim/types/imports/random.d.ts deleted file mode 100644 index 5636224d8..000000000 --- a/packages/preview2-shim/types/imports/random.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -export namespace Random { - export function getRandomBytes(len: bigint): Uint8Array | ArrayBuffer; - export function getRandomU64(): bigint; - export function insecureRandom(): [bigint, bigint]; -} diff --git a/packages/preview2-shim/types/imports/instance-network.d.ts b/packages/preview2-shim/types/imports/sockets-instance-network.d.ts similarity index 55% rename from packages/preview2-shim/types/imports/instance-network.d.ts rename to packages/preview2-shim/types/imports/sockets-instance-network.d.ts index 5e852cf75..1d5a6974b 100644 --- a/packages/preview2-shim/types/imports/instance-network.d.ts +++ b/packages/preview2-shim/types/imports/sockets-instance-network.d.ts @@ -1,4 +1,7 @@ -export namespace InstanceNetwork { +export namespace SocketsInstanceNetwork { + /** + * Get a handle to the default network. + */ export function instanceNetwork(): Network; } import type { Network } from '../imports/network'; diff --git a/packages/preview2-shim/types/imports/sockets-ip-name-lookup.d.ts b/packages/preview2-shim/types/imports/sockets-ip-name-lookup.d.ts new file mode 100644 index 000000000..4374f8438 --- /dev/null +++ b/packages/preview2-shim/types/imports/sockets-ip-name-lookup.d.ts @@ -0,0 +1,76 @@ +export namespace SocketsIpNameLookup { + /** + * Resolve an internet host name to a list of IP addresses. + * + * See the wasi-socket proposal README.md for a comparison with getaddrinfo. + * + * # Parameters + * - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted + * to ASCII using IDNA encoding. + * - `address-family`: If provided, limit the results to addresses of this specific address family. + * - `include-unavailable`: When set to true, this function will also return addresses of which the runtime + * thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on + * systems without an active IPv6 interface. Notes: + * - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. + * - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. + * + * This function never blocks. It either immediately fails or immediately returns successfully with a `resolve-address-stream` + * that can be used to (asynchronously) fetch the results. + * + * At the moment, the stream never completes successfully with 0 items. Ie. the first call + * to `resolve-next-address` never returns `ok(none)`. This may change in the future. + * + * # Typical errors + * - `invalid-name`: `name` is a syntactically invalid domain name. + * - `invalid-name`: `name` is an IP address. + * - `address-family-not-supported`: The specified `address-family` is not supported. (EAI_FAMILY) + * + * # References: + * - + * - + * - + * - + */ + export function resolveAddresses(network: Network, name: string, addressFamily: IpAddressFamily | null, includeUnavailable: boolean): ResolveAddressStream; + /** + * Returns the next address from the resolver. + * + * This function should be called multiple times. On each call, it will + * return the next address in connection order preference. If all + * addresses have been exhausted, this function returns `none`. + * After which, you should release the stream with `drop-resolve-address-stream`. + * + * This function never returns IPv4-mapped IPv6 addresses. + * + * # Typical errors + * - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + * - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + * - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + * - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + */ + export function resolveNextAddress(this: ResolveAddressStream): IpAddress | null; + /** + * Dispose of the specified `resolve-address-stream`, after which it may no longer be used. + * + * Note: this function is scheduled to be removed when Resources are natively supported in Wit. + */ + export function dropResolveAddressStream(this: ResolveAddressStream): void; + /** + * Create a `pollable` which will resolve once the stream is ready for I/O. + * + * Note: this function is here for WASI Preview2 only. + * It's planned to be removed when `future` is natively supported in Preview3. + */ + export function subscribe(this: ResolveAddressStream): Pollable; +} +import type { Pollable } from '../imports/poll'; +export { Pollable }; +import type { Network } from '../imports/network'; +export { Network }; +import type { ErrorCode } from '../imports/network'; +export { ErrorCode }; +import type { IpAddress } from '../imports/network'; +export { IpAddress }; +import type { IpAddressFamily } from '../imports/network'; +export { IpAddressFamily }; +export type ResolveAddressStream = number; diff --git a/packages/preview2-shim/types/imports/sockets-network.d.ts b/packages/preview2-shim/types/imports/sockets-network.d.ts new file mode 100644 index 000000000..54b70ea8a --- /dev/null +++ b/packages/preview2-shim/types/imports/sockets-network.d.ts @@ -0,0 +1,212 @@ +export namespace SocketsNetwork { + /** + * Dispose of the specified `network`, after which it may no longer be used. + * + * Note: this function is scheduled to be removed when Resources are natively supported in Wit. + */ + export function dropNetwork(this: Network): void; +} +/** + * An opaque resource that represents access to (a subset of) the network. + * This enables context-based security for networking. + * There is no need for this to map 1:1 to a physical network interface. + * + * FYI, In the future this will be replaced by handle types. + */ +export type Network = number; +export type Ipv6Address = [number, number, number, number, number, number, number, number]; +export interface Ipv6SocketAddress { + port: number, + flowInfo: number, + address: Ipv6Address, + scopeId: number, +} +export type Ipv4Address = [number, number, number, number]; +export interface Ipv4SocketAddress { + port: number, + address: Ipv4Address, +} +export type IpSocketAddress = IpSocketAddressIpv4 | IpSocketAddressIpv6; +export interface IpSocketAddressIpv4 { + tag: 'ipv4', + val: Ipv4SocketAddress, +} +export interface IpSocketAddressIpv6 { + tag: 'ipv6', + val: Ipv6SocketAddress, +} +/** + * # Variants + * + * ## `"ipv4"` + * + * Similar to `AF_INET` in POSIX. + * + * ## `"ipv6"` + * + * Similar to `AF_INET6` in POSIX. + */ +export type IpAddressFamily = 'ipv4' | 'ipv6'; +export type IpAddress = IpAddressIpv4 | IpAddressIpv6; +export interface IpAddressIpv4 { + tag: 'ipv4', + val: Ipv4Address, +} +export interface IpAddressIpv6 { + tag: 'ipv6', + val: Ipv6Address, +} +/** + * Error codes. + * + * In theory, every API can return any error code. + * In practice, API's typically only return the errors documented per API + * combined with a couple of errors that are always possible: + * - `unknown` + * - `access-denied` + * - `not-supported` + * - `out-of-memory` + * + * See each individual API for what the POSIX equivalents are. They sometimes differ per API. + * + * # Variants + * + * ## `"unknown"` + * + * Unknown error + * + * ## `"access-denied"` + * + * Access denied. + * + * POSIX equivalent: EACCES, EPERM + * + * ## `"not-supported"` + * + * The operation is not supported. + * + * POSIX equivalent: EOPNOTSUPP + * + * ## `"out-of-memory"` + * + * Not enough memory to complete the operation. + * + * POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + * + * ## `"timeout"` + * + * The operation timed out before it could finish completely. + * + * ## `"concurrency-conflict"` + * + * This operation is incompatible with another asynchronous operation that is already in progress. + * + * ## `"not-in-progress"` + * + * Trying to finish an asynchronous operation that: + * - has not been started yet, or: + * - was already finished by a previous `finish-*` call. + * + * Note: this is scheduled to be removed when `future`s are natively supported. + * + * ## `"would-block"` + * + * The operation has been aborted because it could not be completed immediately. + * + * Note: this is scheduled to be removed when `future`s are natively supported. + * + * ## `"address-family-not-supported"` + * + * The specified address-family is not supported. + * + * ## `"address-family-mismatch"` + * + * An IPv4 address was passed to an IPv6 resource, or vice versa. + * + * ## `"invalid-remote-address"` + * + * The socket address is not a valid remote address. E.g. the IP address is set to INADDR_ANY, or the port is set to 0. + * + * ## `"ipv4-only-operation"` + * + * The operation is only supported on IPv4 resources. + * + * ## `"ipv6-only-operation"` + * + * The operation is only supported on IPv6 resources. + * + * ## `"new-socket-limit"` + * + * A new socket resource could not be created because of a system limit. + * + * ## `"already-attached"` + * + * The socket is already attached to another network. + * + * ## `"already-bound"` + * + * The socket is already bound. + * + * ## `"already-connected"` + * + * The socket is already in the Connection state. + * + * ## `"not-bound"` + * + * The socket is not bound to any local address. + * + * ## `"not-connected"` + * + * The socket is not in the Connection state. + * + * ## `"address-not-bindable"` + * + * A bind operation failed because the provided address is not an address that the `network` can bind to. + * + * ## `"address-in-use"` + * + * A bind operation failed because the provided address is already in use. + * + * ## `"ephemeral-ports-exhausted"` + * + * A bind operation failed because there are no ephemeral ports available. + * + * ## `"remote-unreachable"` + * + * The remote address is not reachable + * + * ## `"already-listening"` + * + * The socket is already in the Listener state. + * + * ## `"not-listening"` + * + * The socket is already in the Listener state. + * + * ## `"connection-refused"` + * + * The connection was forcefully rejected + * + * ## `"connection-reset"` + * + * The connection was reset. + * + * ## `"datagram-too-large"` + * + * ## `"invalid-name"` + * + * The provided name is a syntactically invalid domain name. + * + * ## `"name-unresolvable"` + * + * Name does not exist or has no suitable associated IP addresses. + * + * ## `"temporary-resolver-failure"` + * + * A temporary failure in name resolution occurred. + * + * ## `"permanent-resolver-failure"` + * + * A permanent failure in name resolution occurred. + */ +export type ErrorCode = 'unknown' | 'access-denied' | 'not-supported' | 'out-of-memory' | 'timeout' | 'concurrency-conflict' | 'not-in-progress' | 'would-block' | 'address-family-not-supported' | 'address-family-mismatch' | 'invalid-remote-address' | 'ipv4-only-operation' | 'ipv6-only-operation' | 'new-socket-limit' | 'already-attached' | 'already-bound' | 'already-connected' | 'not-bound' | 'not-connected' | 'address-not-bindable' | 'address-in-use' | 'ephemeral-ports-exhausted' | 'remote-unreachable' | 'already-listening' | 'not-listening' | 'connection-refused' | 'connection-reset' | 'datagram-too-large' | 'invalid-name' | 'name-unresolvable' | 'temporary-resolver-failure' | 'permanent-resolver-failure'; diff --git a/packages/preview2-shim/types/imports/sockets-tcp-create-socket.d.ts b/packages/preview2-shim/types/imports/sockets-tcp-create-socket.d.ts new file mode 100644 index 000000000..660ca1fa2 --- /dev/null +++ b/packages/preview2-shim/types/imports/sockets-tcp-create-socket.d.ts @@ -0,0 +1,33 @@ +export namespace SocketsTcpCreateSocket { + /** + * Create a new TCP socket. + * + * Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + * + * This function does not require a network capability handle. This is considered to be safe because + * at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` + * is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + * + * All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + * + * # Typical errors + * - `not-supported`: The host does not support TCP sockets. (EOPNOTSUPP) + * - `address-family-not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + * - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + * + * # References + * - + * - + * - + * - + */ + export function createTcpSocket(addressFamily: IpAddressFamily): TcpSocket; +} +import type { Network } from '../imports/network'; +export { Network }; +import type { ErrorCode } from '../imports/network'; +export { ErrorCode }; +import type { IpAddressFamily } from '../imports/network'; +export { IpAddressFamily }; +import type { TcpSocket } from '../imports/tcp'; +export { TcpSocket }; diff --git a/packages/preview2-shim/types/imports/sockets-tcp.d.ts b/packages/preview2-shim/types/imports/sockets-tcp.d.ts new file mode 100644 index 000000000..4ca834de9 --- /dev/null +++ b/packages/preview2-shim/types/imports/sockets-tcp.d.ts @@ -0,0 +1,285 @@ +export namespace SocketsTcp { + /** + * Bind the socket to a specific network on the provided IP address and port. + * + * If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + * network interface(s) to bind to. + * If the TCP/UDP port is zero, the socket will be bound to a random free port. + * + * When a socket is not explicitly bound, the first invocation to a listen or connect operation will + * implicitly bind the socket. + * + * Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + * + * # Typical `start` errors + * - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + * - `already-bound`: The socket is already bound. (EINVAL) + * - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + * + * # Typical `finish` errors + * - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + * - `address-in-use`: Address is already in use. (EADDRINUSE) + * - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + * - `not-in-progress`: A `bind` operation is not in progress. + * - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + * + * # References + * - + * - + * - + * - + */ + export function startBind(this: TcpSocket, network: Network, localAddress: IpSocketAddress): void; + export function finishBind(this: TcpSocket): void; + /** + * Connect to a remote endpoint. + * + * On success: + * - the socket is transitioned into the Connection state + * - a pair of streams is returned that can be used to read & write to the connection + * + * # Typical `start` errors + * - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + * - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + * - `invalid-remote-address`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + * - `already-attached`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + * - `already-connected`: The socket is already in the Connection state. (EISCONN) + * - `already-listening`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) + * - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + * + * # Typical `finish` errors + * - `timeout`: Connection timed out. (ETIMEDOUT) + * - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + * - `connection-reset`: The connection was reset. (ECONNRESET) + * - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + * - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + * - `not-in-progress`: A `connect` operation is not in progress. + * - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + * + * # References + * - + * - + * - + * - + */ + export function startConnect(this: TcpSocket, network: Network, remoteAddress: IpSocketAddress): void; + export function finishConnect(this: TcpSocket): [InputStream, OutputStream]; + /** + * Start listening for new connections. + * + * Transitions the socket into the Listener state. + * + * Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + * + * # Typical `start` errors + * - `already-attached`: The socket is already attached to a different network. The `network` passed to `listen` must be identical to the one passed to `bind`. + * - `already-connected`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) + * - `already-listening`: The socket is already in the Listener state. + * - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EINVAL on BSD) + * + * # Typical `finish` errors + * - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + * - `not-in-progress`: A `listen` operation is not in progress. + * - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + * + * # References + * - + * - + * - + * - + */ + export function startListen(this: TcpSocket, network: Network): void; + export function finishListen(this: TcpSocket): void; + /** + * Accept a new client socket. + * + * The returned socket is bound and in the Connection state. + * + * On success, this function returns the newly accepted client socket along with + * a pair of streams that can be used to read & write to the connection. + * + * # Typical errors + * - `not-listening`: Socket is not in the Listener state. (EINVAL) + * - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + * + * Host implementations must skip over transient errors returned by the native accept syscall. + * + * # References + * - + * - + * - + * - + */ + export function accept(this: TcpSocket): [TcpSocket, InputStream, OutputStream]; + /** + * Get the bound local address. + * + * # Typical errors + * - `not-bound`: The socket is not bound to any local address. + * + * # References + * - + * - + * - + * - + */ + export function localAddress(this: TcpSocket): IpSocketAddress; + /** + * Get the bound remote address. + * + * # Typical errors + * - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + * + * # References + * - + * - + * - + * - + */ + export function remoteAddress(this: TcpSocket): IpSocketAddress; + /** + * Whether this is a IPv4 or IPv6 socket. + * + * Equivalent to the SO_DOMAIN socket option. + */ + export function addressFamily(this: TcpSocket): IpAddressFamily; + /** + * Whether IPv4 compatibility (dual-stack) mode is disabled or not. + * + * Equivalent to the IPV6_V6ONLY socket option. + * + * # Typical errors + * - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + * - `already-bound`: (set) The socket is already bound. + * - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + * - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + */ + export function ipv6Only(this: TcpSocket): boolean; + export function setIpv6Only(this: TcpSocket, value: boolean): void; + /** + * Hints the desired listen queue size. Implementations are free to ignore this. + * + * # Typical errors + * - `already-connected`: (set) The socket is already in the Connection state. + * - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + */ + export function setListenBacklogSize(this: TcpSocket, value: bigint): void; + /** + * Equivalent to the SO_KEEPALIVE socket option. + * + * # Typical errors + * - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + */ + export function keepAlive(this: TcpSocket): boolean; + export function setKeepAlive(this: TcpSocket, value: boolean): void; + /** + * Equivalent to the TCP_NODELAY socket option. + * + * # Typical errors + * - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + */ + export function noDelay(this: TcpSocket): boolean; + export function setNoDelay(this: TcpSocket, value: boolean): void; + /** + * Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + * + * # Typical errors + * - `already-connected`: (set) The socket is already in the Connection state. + * - `already-listening`: (set) The socket is already in the Listener state. + * - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + */ + export function unicastHopLimit(this: TcpSocket): number; + export function setUnicastHopLimit(this: TcpSocket, value: number): void; + /** + * The kernel buffer space reserved for sends/receives on this socket. + * + * Note #1: an implementation may choose to cap or round the buffer size when setting the value. + * In other words, after setting a value, reading the same setting back may return a different value. + * + * Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + * actual data to be sent/received by the application, because the kernel might also use the buffer space + * for internal metadata structures. + * + * Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + * + * # Typical errors + * - `already-connected`: (set) The socket is already in the Connection state. + * - `already-listening`: (set) The socket is already in the Listener state. + * - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + */ + export function receiveBufferSize(this: TcpSocket): bigint; + export function setReceiveBufferSize(this: TcpSocket, value: bigint): void; + export function sendBufferSize(this: TcpSocket): bigint; + export function setSendBufferSize(this: TcpSocket, value: bigint): void; + /** + * Create a `pollable` which will resolve once the socket is ready for I/O. + * + * Note: this function is here for WASI Preview2 only. + * It's planned to be removed when `future` is natively supported in Preview3. + */ + export function subscribe(this: TcpSocket): Pollable; + /** + * Initiate a graceful shutdown. + * + * - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + * operations on the `input-stream` associated with this socket will return an End Of Stream indication. + * Any data still in the receive queue at time of calling `shutdown` will be discarded. + * - send: the socket is not expecting to send any more data to the peer. All subsequent write + * operations on the `output-stream` associated with this socket will return an error. + * - both: same effect as receive & send combined. + * + * The shutdown function does not close (drop) the socket. + * + * # Typical errors + * - `not-connected`: The socket is not in the Connection state. (ENOTCONN) + * + * # References + * - + * - + * - + * - + */ + export function shutdown(this: TcpSocket, shutdownType: ShutdownType): void; + /** + * Dispose of the specified `tcp-socket`, after which it may no longer be used. + * + * Similar to the POSIX `close` function. + * + * Note: this function is scheduled to be removed when Resources are natively supported in Wit. + */ + export function dropTcpSocket(this: TcpSocket): void; +} +import type { InputStream } from '../imports/streams'; +export { InputStream }; +import type { OutputStream } from '../imports/streams'; +export { OutputStream }; +import type { Pollable } from '../imports/poll'; +export { Pollable }; +import type { Network } from '../imports/network'; +export { Network }; +import type { ErrorCode } from '../imports/network'; +export { ErrorCode }; +import type { IpSocketAddress } from '../imports/network'; +export { IpSocketAddress }; +import type { IpAddressFamily } from '../imports/network'; +export { IpAddressFamily }; +/** + * A TCP socket handle. + */ +export type TcpSocket = number; +/** + * # Variants + * + * ## `"receive"` + * + * Similar to `SHUT_RD` in POSIX. + * + * ## `"send"` + * + * Similar to `SHUT_WR` in POSIX. + * + * ## `"both"` + * + * Similar to `SHUT_RDWR` in POSIX. + */ +export type ShutdownType = 'receive' | 'send' | 'both'; diff --git a/packages/preview2-shim/types/imports/sockets-udp-create-socket.d.ts b/packages/preview2-shim/types/imports/sockets-udp-create-socket.d.ts new file mode 100644 index 000000000..4cc43618f --- /dev/null +++ b/packages/preview2-shim/types/imports/sockets-udp-create-socket.d.ts @@ -0,0 +1,33 @@ +export namespace SocketsUdpCreateSocket { + /** + * Create a new UDP socket. + * + * Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + * + * This function does not require a network capability handle. This is considered to be safe because + * at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` is called, + * the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + * + * All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + * + * # Typical errors + * - `not-supported`: The host does not support UDP sockets. (EOPNOTSUPP) + * - `address-family-not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + * - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + * + * # References: + * - + * - + * - + * - + */ + export function createUdpSocket(addressFamily: IpAddressFamily): UdpSocket; +} +import type { Network } from '../imports/network'; +export { Network }; +import type { ErrorCode } from '../imports/network'; +export { ErrorCode }; +import type { IpAddressFamily } from '../imports/network'; +export { IpAddressFamily }; +import type { UdpSocket } from '../imports/udp'; +export { UdpSocket }; diff --git a/packages/preview2-shim/types/imports/sockets-udp.d.ts b/packages/preview2-shim/types/imports/sockets-udp.d.ts new file mode 100644 index 000000000..1b02c771e --- /dev/null +++ b/packages/preview2-shim/types/imports/sockets-udp.d.ts @@ -0,0 +1,219 @@ +export namespace SocketsUdp { + /** + * Bind the socket to a specific network on the provided IP address and port. + * + * If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + * network interface(s) to bind to. + * If the TCP/UDP port is zero, the socket will be bound to a random free port. + * + * When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. + * + * Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + * + * # Typical `start` errors + * - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + * - `already-bound`: The socket is already bound. (EINVAL) + * - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + * + * # Typical `finish` errors + * - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + * - `address-in-use`: Address is already in use. (EADDRINUSE) + * - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + * - `not-in-progress`: A `bind` operation is not in progress. + * - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + * + * # References + * - + * - + * - + * - + */ + export function startBind(this: UdpSocket, network: Network, localAddress: IpSocketAddress): void; + export function finishBind(this: UdpSocket): void; + /** + * Set the destination address. + * + * The local-address is updated based on the best network path to `remote-address`. + * + * When a destination address is set: + * - all receive operations will only return datagrams sent from the provided `remote-address`. + * - the `send` function can only be used to send to this destination. + * + * Note that this function does not generate any network traffic and the peer is not aware of this "connection". + * + * Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + * + * # Typical `start` errors + * - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + * - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + * - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + * - `already-attached`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + * - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + * + * # Typical `finish` errors + * - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + * - `not-in-progress`: A `connect` operation is not in progress. + * - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + * + * # References + * - + * - + * - + * - + */ + export function startConnect(this: UdpSocket, network: Network, remoteAddress: IpSocketAddress): void; + export function finishConnect(this: UdpSocket): void; + /** + * Receive a message. + * + * Returns: + * - The sender address of the datagram + * - The number of bytes read. + * + * # Typical errors + * - `not-bound`: The socket is not bound to any local address. (EINVAL) + * - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + * - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) + * + * # References + * - + * - + * - + * - + * - + * - + * - + */ + export function receive(this: UdpSocket): Datagram; + /** + * Send a message to a specific destination address. + * + * The remote address option is required. To send a message to the "connected" peer, + * call `remote-address` to get their address. + * + * # Typical errors + * - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + * - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + * - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + * - `already-connected`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) + * - `not-bound`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. + * - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + * - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + * - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) + * + * # References + * - + * - + * - + * - + * - + * - + * - + */ + export function send(this: UdpSocket, datagram: Datagram): void; + /** + * Get the current bound address. + * + * # Typical errors + * - `not-bound`: The socket is not bound to any local address. + * + * # References + * - + * - + * - + * - + */ + export function localAddress(this: UdpSocket): IpSocketAddress; + /** + * Get the address set with `connect`. + * + * # Typical errors + * - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + * + * # References + * - + * - + * - + * - + */ + export function remoteAddress(this: UdpSocket): IpSocketAddress; + /** + * Whether this is a IPv4 or IPv6 socket. + * + * Equivalent to the SO_DOMAIN socket option. + */ + export function addressFamily(this: UdpSocket): IpAddressFamily; + /** + * Whether IPv4 compatibility (dual-stack) mode is disabled or not. + * + * Equivalent to the IPV6_V6ONLY socket option. + * + * # Typical errors + * - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + * - `already-bound`: (set) The socket is already bound. + * - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + * - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + */ + export function ipv6Only(this: UdpSocket): boolean; + export function setIpv6Only(this: UdpSocket, value: boolean): void; + /** + * Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + * + * # Typical errors + * - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + */ + export function unicastHopLimit(this: UdpSocket): number; + export function setUnicastHopLimit(this: UdpSocket, value: number): void; + /** + * The kernel buffer space reserved for sends/receives on this socket. + * + * Note #1: an implementation may choose to cap or round the buffer size when setting the value. + * In other words, after setting a value, reading the same setting back may return a different value. + * + * Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + * actual data to be sent/received by the application, because the kernel might also use the buffer space + * for internal metadata structures. + * + * Fails when this socket is in the Listening state. + * + * Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + * + * # Typical errors + * - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + */ + export function receiveBufferSize(this: UdpSocket): bigint; + export function setReceiveBufferSize(this: UdpSocket, value: bigint): void; + export function sendBufferSize(this: UdpSocket): bigint; + export function setSendBufferSize(this: UdpSocket, value: bigint): void; + /** + * Create a `pollable` which will resolve once the socket is ready for I/O. + * + * Note: this function is here for WASI Preview2 only. + * It's planned to be removed when `future` is natively supported in Preview3. + */ + export function subscribe(this: UdpSocket): Pollable; + /** + * Dispose of the specified `udp-socket`, after which it may no longer be used. + * + * Note: this function is scheduled to be removed when Resources are natively supported in Wit. + */ + export function dropUdpSocket(this: UdpSocket): void; +} +import type { Pollable } from '../imports/poll'; +export { Pollable }; +import type { Network } from '../imports/network'; +export { Network }; +import type { ErrorCode } from '../imports/network'; +export { ErrorCode }; +import type { IpSocketAddress } from '../imports/network'; +export { IpSocketAddress }; +import type { IpAddressFamily } from '../imports/network'; +export { IpAddressFamily }; +/** + * A UDP socket handle. + */ +export type UdpSocket = number; +export interface Datagram { + data: Uint8Array, + remoteAddress: IpSocketAddress, +} diff --git a/packages/preview2-shim/types/imports/streams.d.ts b/packages/preview2-shim/types/imports/streams.d.ts deleted file mode 100644 index ade09fd90..000000000 --- a/packages/preview2-shim/types/imports/streams.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -export namespace Streams { - export function read(this: InputStream, len: bigint): [Uint8Array | ArrayBuffer, boolean]; - export function blockingRead(this: InputStream, len: bigint): [Uint8Array | ArrayBuffer, boolean]; - export function skip(this: InputStream, len: bigint): [bigint, boolean]; - export function blockingSkip(this: InputStream, len: bigint): [bigint, boolean]; - export function subscribeToInputStream(this: InputStream): Pollable; - export function dropInputStream(this: InputStream): void; - export function write(this: OutputStream, buf: Uint8Array): bigint; - export function blockingWrite(this: OutputStream, buf: Uint8Array): bigint; - export function writeZeroes(this: OutputStream, len: bigint): bigint; - export function blockingWriteZeroes(this: OutputStream, len: bigint): bigint; - export function splice(this: OutputStream, src: InputStream, len: bigint): [bigint, boolean]; - export function blockingSplice(this: OutputStream, src: InputStream, len: bigint): [bigint, boolean]; - export function forward(this: OutputStream, src: InputStream): bigint; - export function subscribeToOutputStream(this: OutputStream): Pollable; - export function dropOutputStream(this: OutputStream): void; -} -export type InputStream = number; -export interface StreamError { -} -import type { Pollable } from '../imports/poll'; -export { Pollable }; -export type OutputStream = number; diff --git a/packages/preview2-shim/types/imports/tcp-create-socket.d.ts b/packages/preview2-shim/types/imports/tcp-create-socket.d.ts deleted file mode 100644 index b4f53f0f7..000000000 --- a/packages/preview2-shim/types/imports/tcp-create-socket.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export namespace TcpCreateSocket { - export function createTcpSocket(addressFamily: IpAddressFamily): TcpSocket; -} -import type { IpAddressFamily } from '../imports/network'; -export { IpAddressFamily }; -import type { TcpSocket } from '../imports/tcp'; -export { TcpSocket }; -import type { Error } from '../imports/network'; -export { Error }; diff --git a/packages/preview2-shim/types/imports/tcp.d.ts b/packages/preview2-shim/types/imports/tcp.d.ts deleted file mode 100644 index a7d607ab4..000000000 --- a/packages/preview2-shim/types/imports/tcp.d.ts +++ /dev/null @@ -1,52 +0,0 @@ -export namespace Tcp { - export function bind(this: TcpSocket, network: Network, localAddress: IpSocketAddress): void; - export function connect(this: TcpSocket, network: Network, remoteAddress: IpSocketAddress): [InputStream, OutputStream]; - export function listen(this: TcpSocket, network: Network): void; - export function accept(this: TcpSocket): [TcpSocket, InputStream, OutputStream]; - export function localAddress(this: TcpSocket): IpSocketAddress; - export function remoteAddress(this: TcpSocket): IpSocketAddress; - export function addressFamily(this: TcpSocket): IpAddressFamily; - export function ipv6Only(this: TcpSocket): boolean; - export function setIpv6Only(this: TcpSocket, value: boolean): void; - export function setListenBacklogSize(this: TcpSocket, value: bigint): void; - export function keepAlive(this: TcpSocket): boolean; - export function setKeepAlive(this: TcpSocket, value: boolean): void; - export function noDelay(this: TcpSocket): boolean; - export function setNoDelay(this: TcpSocket, value: boolean): void; - export function unicastHopLimit(this: TcpSocket): number; - export function setUnicastHopLimit(this: TcpSocket, value: number): void; - export function receiveBufferSize(this: TcpSocket): bigint; - export function setReceiveBufferSize(this: TcpSocket, value: bigint): void; - export function sendBufferSize(this: TcpSocket): bigint; - export function setSendBufferSize(this: TcpSocket, value: bigint): void; - export function nonBlocking(this: TcpSocket): boolean; - export function setNonBlocking(this: TcpSocket, value: boolean): void; - export function subscribe(this: TcpSocket): Pollable; - export function shutdown(this: TcpSocket, shutdownType: ShutdownType): void; - export function dropTcpSocket(this: TcpSocket): void; -} -export type TcpSocket = number; -import type { Network } from '../imports/network'; -export { Network }; -import type { IpSocketAddress } from '../imports/network'; -export { IpSocketAddress }; -import type { Error } from '../imports/network'; -export { Error }; -import type { InputStream } from '../imports/streams'; -export { InputStream }; -import type { OutputStream } from '../imports/streams'; -export { OutputStream }; -import type { IpAddressFamily } from '../imports/network'; -export { IpAddressFamily }; -import type { Pollable } from '../imports/poll'; -export { Pollable }; -/** - * # Variants - * - * ## `"receive"` - * - * ## `"send"` - * - * ## `"both"` - */ -export type ShutdownType = 'receive' | 'send' | 'both'; diff --git a/packages/preview2-shim/types/imports/timezone.d.ts b/packages/preview2-shim/types/imports/timezone.d.ts deleted file mode 100644 index a3a065375..000000000 --- a/packages/preview2-shim/types/imports/timezone.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export namespace Timezone { - export function display(this: Timezone, when: Datetime): TimezoneDisplay; - export function utcOffset(this: Timezone, when: Datetime): number; - export function dropTimezone(this: Timezone): void; -} -export type Timezone = number; -import type { Datetime } from '../imports/wall-clock'; -export { Datetime }; -export interface TimezoneDisplay { - utcOffset: number, - name: string, - inDaylightSavingTime: boolean, -} diff --git a/packages/preview2-shim/types/imports/udp-create-socket.d.ts b/packages/preview2-shim/types/imports/udp-create-socket.d.ts deleted file mode 100644 index ddce04657..000000000 --- a/packages/preview2-shim/types/imports/udp-create-socket.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export namespace UdpCreateSocket { - export function createUdpSocket(addressFamily: IpAddressFamily): UdpSocket; -} -import type { IpAddressFamily } from '../imports/network'; -export { IpAddressFamily }; -import type { UdpSocket } from '../imports/udp'; -export { UdpSocket }; -import type { Error } from '../imports/network'; -export { Error }; diff --git a/packages/preview2-shim/types/imports/udp.d.ts b/packages/preview2-shim/types/imports/udp.d.ts deleted file mode 100644 index 31030a5d6..000000000 --- a/packages/preview2-shim/types/imports/udp.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -export namespace Udp { - export function bind(this: UdpSocket, network: Network, localAddress: IpSocketAddress): void; - export function connect(this: UdpSocket, network: Network, remoteAddress: IpSocketAddress): void; - export function receive(this: UdpSocket): Datagram; - export function send(this: UdpSocket, datagram: Datagram): void; - export function localAddress(this: UdpSocket): IpSocketAddress; - export function remoteAddress(this: UdpSocket): IpSocketAddress; - export function addressFamily(this: UdpSocket): IpAddressFamily; - export function ipv6Only(this: UdpSocket): boolean; - export function setIpv6Only(this: UdpSocket, value: boolean): void; - export function unicastHopLimit(this: UdpSocket): number; - export function setUnicastHopLimit(this: UdpSocket, value: number): void; - export function receiveBufferSize(this: UdpSocket): bigint; - export function setReceiveBufferSize(this: UdpSocket, value: bigint): void; - export function sendBufferSize(this: UdpSocket): bigint; - export function setSendBufferSize(this: UdpSocket, value: bigint): void; - export function nonBlocking(this: UdpSocket): boolean; - export function setNonBlocking(this: UdpSocket, value: boolean): void; - export function subscribe(this: UdpSocket): Pollable; - export function dropUdpSocket(this: UdpSocket): void; -} -export type UdpSocket = number; -import type { Network } from '../imports/network'; -export { Network }; -import type { IpSocketAddress } from '../imports/network'; -export { IpSocketAddress }; -import type { Error } from '../imports/network'; -export { Error }; -export interface Datagram { - data: Uint8Array, - remoteAddress: IpSocketAddress, -} -import type { IpAddressFamily } from '../imports/network'; -export { IpAddressFamily }; -import type { Pollable } from '../imports/poll'; -export { Pollable }; diff --git a/packages/preview2-shim/types/imports/wall-clock.d.ts b/packages/preview2-shim/types/imports/wall-clock.d.ts deleted file mode 100644 index 6c96c5b29..000000000 --- a/packages/preview2-shim/types/imports/wall-clock.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export namespace WallClock { - export function now(): Datetime; - export function resolution(): Datetime; -} -export interface Datetime { - seconds: bigint, - nanoseconds: number, -} diff --git a/packages/preview2-shim/types/index.d.ts b/packages/preview2-shim/types/index.d.ts deleted file mode 100644 index 0cf5d647a..000000000 --- a/packages/preview2-shim/types/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ImportObject as WasiProxyImportObject } from "./wasi-proxy"; -import { ImportObject as WasiReactorImportObject } from "./wasi-reactor"; - -export type ImportObject = WasiProxyImportObject & WasiReactorImportObject; - -export declare const importObject: ImportObject; - -export default importObject; diff --git a/packages/preview2-shim/types/wasi-command.d.ts b/packages/preview2-shim/types/wasi-command.d.ts new file mode 100644 index 000000000..b3af6a1f4 --- /dev/null +++ b/packages/preview2-shim/types/wasi-command.d.ts @@ -0,0 +1,23 @@ +import { CliBaseEnvironment as CliBaseEnvironmentImports } from './imports/cli-base-environment'; +import { CliBasePreopens as CliBasePreopensImports } from './imports/cli-base-preopens'; +import { CliBaseExit as CliBaseExitImports } from './imports/cli-base-exit'; +import { CliBaseStdin as CliBaseStdinImports } from './imports/cli-base-stdin'; +import { CliBaseStdout as CliBaseStdoutImports } from './imports/cli-base-stdout'; +import { CliBaseStderr as CliBaseStderrImports } from './imports/cli-base-stderr'; +import { ClocksWallClock as ClocksWallClockImports } from './imports/clocks-wall-clock'; +import { ClocksMonotonicClock as ClocksMonotonicClockImports } from './imports/clocks-monotonic-clock'; +import { ClocksTimezone as ClocksTimezoneImports } from './imports/clocks-timezone'; +import { FilesystemFilesystem as FilesystemFilesystemImports } from './imports/filesystem-filesystem'; +import { IoStreams as IoStreamsImports } from './imports/io-streams'; +import { PollPoll as PollPollImports } from './imports/poll-poll'; +import { RandomRandom as RandomRandomImports } from './imports/random-random'; +import { RandomInsecure as RandomInsecureImports } from './imports/random-insecure'; +import { RandomInsecureSeed as RandomInsecureSeedImports } from './imports/random-insecure-seed'; +import { SocketsNetwork as SocketsNetworkImports } from './imports/sockets-network'; +import { SocketsInstanceNetwork as SocketsInstanceNetworkImports } from './imports/sockets-instance-network'; +import { SocketsIpNameLookup as SocketsIpNameLookupImports } from './imports/sockets-ip-name-lookup'; +import { SocketsTcp as SocketsTcpImports } from './imports/sockets-tcp'; +import { SocketsTcpCreateSocket as SocketsTcpCreateSocketImports } from './imports/sockets-tcp-create-socket'; +import { SocketsUdp as SocketsUdpImports } from './imports/sockets-udp'; +import { SocketsUdpCreateSocket as SocketsUdpCreateSocketImports } from './imports/sockets-udp-create-socket'; +export function run(): void; diff --git a/packages/preview2-shim/types/wasi-proxy.d.ts b/packages/preview2-shim/types/wasi-proxy.d.ts index d66c8b653..afa23fd53 100644 --- a/packages/preview2-shim/types/wasi-proxy.d.ts +++ b/packages/preview2-shim/types/wasi-proxy.d.ts @@ -1,44 +1,11 @@ -import { Random as RandomImports } from './imports/random'; -import { Console as ConsoleImports } from './imports/console'; -import { Poll as PollImports } from './imports/poll'; -import { Streams as StreamsImports } from './imports/streams'; -import { Types as TypesImports } from './imports/types'; -import { DefaultOutgoingHttp as DefaultOutgoingHttpImports } from './imports/default-outgoing-HTTP'; -import { Http as HttpExports } from './exports/HTTP'; -export interface ImportObject { - 'random': typeof RandomImports, - 'console': typeof ConsoleImports, - 'poll': typeof PollImports, - 'streams': typeof StreamsImports, - 'types': typeof TypesImports, - 'default-outgoing-HTTP': typeof DefaultOutgoingHttpImports, -} -export interface WasiProxy { - 'HTTP': typeof HttpExports, -} - -/** -* Instantiates this component with the provided imports and -* returns a map of all the exports of the component. -* -* This function is intended to be similar to the -* `WebAssembly.instantiate` function. The second `imports` -* argument is the "import object" for wasm, except here it -* uses component-model-layer types instead of core wasm -* integers/numbers/etc. -* -* The first argument to this function, `compileCore`, is -* used to compile core wasm modules within the component. -* Components are composed of core wasm modules and this callback -* will be invoked per core wasm module. The caller of this -* function is responsible for reading the core wasm module -* identified by `path` and returning its compiled -* WebAssembly.Module object. This would use `compileStreaming` -* on the web, for example. -*/ -export function instantiate( -compileCore: (path: string, imports: Record) => Promise, -imports: ImportObject, -instantiateCore?: (module: WebAssembly.Module, imports: Record) => Promise -): Promise; - +import { HttpTypes as HttpTypesImports } from './imports/http-types'; +import { HttpOutgoingHandler as HttpOutgoingHandlerImports } from './imports/http-outgoing-handler'; +import { IoStreams as IoStreamsImports } from './imports/io-streams'; +import { LoggingHandler as LoggingHandlerImports } from './imports/logging-handler'; +import { PollPoll as PollPollImports } from './imports/poll-poll'; +import { RandomRandom as RandomRandomImports } from './imports/random-random'; +import { RandomInsecure as RandomInsecureImports } from './imports/random-insecure'; +import { RandomInsecureSeed as RandomInsecureSeedImports } from './imports/random-insecure-seed'; +import { HttpIncomingHandler as HttpIncomingHandlerExports } from './exports/http-incoming-handler'; +export const httpIncomingHandler: typeof HttpIncomingHandlerExports; +export const incomingHandler: typeof HttpIncomingHandlerExports; diff --git a/packages/preview2-shim/types/wasi-reactor.d.ts b/packages/preview2-shim/types/wasi-reactor.d.ts index 8cd6183c5..255ffbda9 100644 --- a/packages/preview2-shim/types/wasi-reactor.d.ts +++ b/packages/preview2-shim/types/wasi-reactor.d.ts @@ -1,70 +1,23 @@ -import { WallClock as WallClockImports } from './imports/wall-clock'; -import { Poll as PollImports } from './imports/poll'; -import { MonotonicClock as MonotonicClockImports } from './imports/monotonic-clock'; -import { Timezone as TimezoneImports } from './imports/timezone'; -import { Streams as StreamsImports } from './imports/streams'; -import { Filesystem as FilesystemImports } from './imports/filesystem'; -import { Network as NetworkImports } from './imports/network'; -import { InstanceNetwork as InstanceNetworkImports } from './imports/instance-network'; -import { IpNameLookup as IpNameLookupImports } from './imports/ip-name-lookup'; -import { Tcp as TcpImports } from './imports/tcp'; -import { TcpCreateSocket as TcpCreateSocketImports } from './imports/tcp-create-socket'; -import { Udp as UdpImports } from './imports/udp'; -import { UdpCreateSocket as UdpCreateSocketImports } from './imports/udp-create-socket'; -import { Random as RandomImports } from './imports/random'; -import { Console as ConsoleImports } from './imports/console'; -import { Types as TypesImports } from './imports/types'; -import { DefaultOutgoingHttp as DefaultOutgoingHttpImports } from './imports/default-outgoing-HTTP'; -import { Environment as EnvironmentImports } from './imports/environment'; -import { Preopens as PreopensImports } from './imports/preopens'; -import { Exit as ExitImports } from './imports/exit'; -export interface ImportObject { - 'wall-clock': typeof WallClockImports, - 'poll': typeof PollImports, - 'monotonic-clock': typeof MonotonicClockImports, - 'timezone': typeof TimezoneImports, - 'streams': typeof StreamsImports, - 'filesystem': typeof FilesystemImports, - 'network': typeof NetworkImports, - 'instance-network': typeof InstanceNetworkImports, - 'ip-name-lookup': typeof IpNameLookupImports, - 'tcp': typeof TcpImports, - 'tcp-create-socket': typeof TcpCreateSocketImports, - 'udp': typeof UdpImports, - 'udp-create-socket': typeof UdpCreateSocketImports, - 'random': typeof RandomImports, - 'console': typeof ConsoleImports, - 'types': typeof TypesImports, - 'default-outgoing-HTTP': typeof DefaultOutgoingHttpImports, - 'environment': typeof EnvironmentImports, - 'preopens': typeof PreopensImports, - 'exit': typeof ExitImports, -} -export interface WasiReactor { -} - -/** -* Instantiates this component with the provided imports and -* returns a map of all the exports of the component. -* -* This function is intended to be similar to the -* `WebAssembly.instantiate` function. The second `imports` -* argument is the "import object" for wasm, except here it -* uses component-model-layer types instead of core wasm -* integers/numbers/etc. -* -* The first argument to this function, `compileCore`, is -* used to compile core wasm modules within the component. -* Components are composed of core wasm modules and this callback -* will be invoked per core wasm module. The caller of this -* function is responsible for reading the core wasm module -* identified by `path` and returning its compiled -* WebAssembly.Module object. This would use `compileStreaming` -* on the web, for example. -*/ -export function instantiate( -compileCore: (path: string, imports: Record) => Promise, -imports: ImportObject, -instantiateCore?: (module: WebAssembly.Module, imports: Record) => Promise -): Promise; - +import { CliBaseEnvironment as CliBaseEnvironmentImports } from './imports/cli-base-environment'; +import { CliBasePreopens as CliBasePreopensImports } from './imports/cli-base-preopens'; +import { CliBaseExit as CliBaseExitImports } from './imports/cli-base-exit'; +import { CliBaseStdin as CliBaseStdinImports } from './imports/cli-base-stdin'; +import { CliBaseStdout as CliBaseStdoutImports } from './imports/cli-base-stdout'; +import { CliBaseStderr as CliBaseStderrImports } from './imports/cli-base-stderr'; +import { ClocksWallClock as ClocksWallClockImports } from './imports/clocks-wall-clock'; +import { ClocksMonotonicClock as ClocksMonotonicClockImports } from './imports/clocks-monotonic-clock'; +import { ClocksTimezone as ClocksTimezoneImports } from './imports/clocks-timezone'; +import { FilesystemFilesystem as FilesystemFilesystemImports } from './imports/filesystem-filesystem'; +import { HttpTypes as HttpTypesImports } from './imports/http-types'; +import { HttpOutgoingHandler as HttpOutgoingHandlerImports } from './imports/http-outgoing-handler'; +import { IoStreams as IoStreamsImports } from './imports/io-streams'; +import { LoggingHandler as LoggingHandlerImports } from './imports/logging-handler'; +import { PollPoll as PollPollImports } from './imports/poll-poll'; +import { RandomRandom as RandomRandomImports } from './imports/random-random'; +import { SocketsNetwork as SocketsNetworkImports } from './imports/sockets-network'; +import { SocketsInstanceNetwork as SocketsInstanceNetworkImports } from './imports/sockets-instance-network'; +import { SocketsIpNameLookup as SocketsIpNameLookupImports } from './imports/sockets-ip-name-lookup'; +import { SocketsTcp as SocketsTcpImports } from './imports/sockets-tcp'; +import { SocketsTcpCreateSocket as SocketsTcpCreateSocketImports } from './imports/sockets-tcp-create-socket'; +import { SocketsUdp as SocketsUdpImports } from './imports/sockets-udp'; +import { SocketsUdpCreateSocket as SocketsUdpCreateSocketImports } from './imports/sockets-udp-create-socket'; diff --git a/src/api.js b/src/api.js index 324b60daa..1925fe6f5 100644 --- a/src/api.js +++ b/src/api.js @@ -1,10 +1,9 @@ export { optimizeComponent as opt } from './cmd/opt.js'; export { transpileComponent as transpile } from './cmd/transpile.js'; -import { exports } from '../obj/wasm-tools.js'; -export const { parse, print, componentNew, componentWit, componentEmbed, metadataAdd, metadataShow } = exports; +export * from '../obj/wasm-tools.js'; export function preview1AdapterCommandPath () { - return new URL('../lib/wasi_preview1_component_adapter.command.wasm', import.meta.url); + return new URL('../lib/wasi_snapshot_preview1.command.wasm', import.meta.url); } export function preview1AdapterReactorPath () { - return new URL('../lib/wasi_preview1_component_adapter.reactor.wasm', import.meta.url); + return new URL('../lib/wasi_snapshot_preview1.reactor.wasm', import.meta.url); } diff --git a/src/cmd/componentize.js b/src/cmd/componentize.js index d2a056a89..54f4ba250 100644 --- a/src/cmd/componentize.js +++ b/src/cmd/componentize.js @@ -7,7 +7,7 @@ export async function componentize (jsSource, opts) { try { ({ componentize: componentizeFn } = await eval('import("@bytecodealliance/componentize-js")')); } catch (e) { - if (e?.code === 'ERR_MODULE_NOT_FOUND') + if (e?.code === 'ERR_MODULE_NOT_FOUND' && e?.message?.includes('\'@bytecodealliance/componentize-js\'')) throw new Error(`componentize-js must first be installed separately via "npm install @bytecodealliance/componentize-js".`); throw e; } diff --git a/src/cmd/opt.js b/src/cmd/opt.js index aa1d2cf3f..00f71fe4d 100644 --- a/src/cmd/opt.js +++ b/src/cmd/opt.js @@ -1,12 +1,10 @@ -import { exports } from '../../obj/wasm-tools.js'; +import { metadataShow, print } from '../../obj/wasm-tools.js'; import { writeFile } from 'fs/promises'; import { fileURLToPath } from 'url'; import c from 'chalk-template'; import { readFile, sizeStr, fixedDigitDisplay, table, spawnIOTmp, setShowSpinner, getShowSpinner } from '../common.js'; import ora from 'ora'; -const { metadataShow, print } = exports; - let WASM_OPT; try { WASM_OPT = fileURLToPath(new URL('../../node_modules/binaryen/bin/wasm-opt', import.meta.url)); diff --git a/src/cmd/transpile.js b/src/cmd/transpile.js index 7c21f0c6d..925e6c758 100644 --- a/src/cmd/transpile.js +++ b/src/cmd/transpile.js @@ -1,4 +1,4 @@ -import { exports } from '../../obj/js-component-bindgen-component.js'; +import { generate } from '../../obj/js-component-bindgen-component.js'; import { writeFile } from 'fs/promises'; import { mkdir } from 'fs/promises'; import { dirname, extname, basename } from 'path'; @@ -9,9 +9,6 @@ import { minify } from 'terser'; import { fileURLToPath } from 'url'; import ora from 'ora'; -// pending default export support -const { generate } = exports; - export async function transpile (componentPath, opts, program) { const varIdx = program.parent.rawArgs.indexOf('--'); if (varIdx !== -1) @@ -92,36 +89,7 @@ export async function transpileComponent (component, opts = {}) { if (opts.wasiShim !== false) { opts.map = Object.assign({ - // Deprecated - 'environment-preopens': '@bytecodealliance/preview2-shim/environment-preopens', - 'console': '@bytecodealliance/preview2-shim/console', - 'default-outgoing-HTTP': '@bytecodealliance/preview2-shim/default-outgoing-HTTP', - 'environment': '@bytecodealliance/preview2-shim/environment', - // Deprecated - 'environment-preopens': '@bytecodealliance/preview2-shim/environment-preopens', - 'exit': '@bytecodealliance/preview2-shim/exit', - 'filesystem': '@bytecodealliance/preview2-shim/filesystem', - // Deprecated - 'instance-monotonic-clock': '@bytecodealliance/preview2-shim/instance-monotonic-clock', - // Deprecated - 'instance-wall-clock': '@bytecodealliance/preview2-shim/instance-wall-clock', - 'instance-network': '@bytecodealliance/preview2-shim/instance-network', - 'ip-name-lookup': '@bytecodealliance/preview2-shim/ip-name-lookup', - 'monotonic-clock': '@bytecodealliance/preview2-shim/monotonic-clock', - 'network': '@bytecodealliance/preview2-shim/network', - 'poll': '@bytecodealliance/preview2-shim/poll', - 'preopens': '@bytecodealliance/preview2-shim/preopens', - 'random': '@bytecodealliance/preview2-shim/random', - // Deprecated - 'stderr': '@bytecodealliance/preview2-shim/stderr', - 'streams': '@bytecodealliance/preview2-shim/streams', - 'tcp-create-socket': '@bytecodealliance/preview2-shim/tcp-create-socket', - 'tcp': '@bytecodealliance/preview2-shim/tcp', - 'timezone': '@bytecodealliance/preview2-shim/timezone', - 'types': '@bytecodealliance/preview2-shim/types', - 'udp-create-socket': '@bytecodealliance/preview2-shim/udp-create-socket', - 'udp': '@bytecodealliance/preview2-shim/udp', - 'wall-clock': '@bytecodealliance/preview2-shim/wall-clock' + 'wasi:*': '@bytecodealliance/preview2-shim/*' }, opts.map || {}); } @@ -181,10 +149,6 @@ export async function transpileComponent (component, opts = {}) { .replace(/export var ([^ ]+) = ([^. ]+)\.([^ ]+);/g, '') .replace(/var retasmFunc = [\s\S]*$/, '') .replace(/var memasmFunc = new ArrayBuffer\(0\);/g, '') - // "imports" as the name of the imports clashes with internal asm.js naming - // so we have to do a late patchup - .replace('var imports = imports.imports;', 'var imports_ = imports.imports;') - .replace(/([^$]) = imports\["/g, '$1 = imports_["') .replace('memory.grow = __wasm_memory_grow;', '') .trim() }`).join(',\n'); diff --git a/src/cmd/wasm-tools.js b/src/cmd/wasm-tools.js index e60674b81..1392063e4 100644 --- a/src/cmd/wasm-tools.js +++ b/src/cmd/wasm-tools.js @@ -1,19 +1,9 @@ import { writeFile } from "node:fs/promises"; import { readFile } from '../common.js'; -import { exports } from "../../obj/wasm-tools.js"; +import { print as printFn, parse as parseFn, componentWit as componentWitFn, componentNew as componentNewFn, componentEmbed as componentEmbedFn, metadataAdd as metadataAddFn, metadataShow as metadataShowFn } from "../../obj/wasm-tools.js"; import { resolve, basename, extname } from 'node:path'; import c from 'chalk-template'; -const { - print: printFn, - parse: parseFn, - componentWit: componentWitFn, - componentNew: componentNewFn, - componentEmbed: componentEmbedFn, - metadataAdd: metadataAddFn, - metadataShow: metadataShowFn, -} = exports; - export async function parse(file, opts) { const source = (await readFile(file)).toString(); const output = parseFn(source); diff --git a/test/api.js b/test/api.js index 54141d15d..c8136fa37 100644 --- a/test/api.js +++ b/test/api.js @@ -10,8 +10,8 @@ export async function apiTest (fixtures) { const component = await readFile(`test/fixtures/components/${name}.component.wasm`); const { files, imports, exports } = await transpile(component, { name }); strictEqual(imports.length, 2); - strictEqual(exports.length, 2); - deepStrictEqual(exports[0], ['exports', 'instance']); + strictEqual(exports.length, 3); + deepStrictEqual(exports[0], ['flavorfulTest', 'instance']); ok(files[name + '.js']); }); @@ -27,8 +27,8 @@ export async function apiTest (fixtures) { base64Cutoff: 0, }); strictEqual(imports.length, 2); - strictEqual(exports.length, 2); - deepStrictEqual(exports[0], ['exports', 'instance']); + strictEqual(exports.length, 3); + deepStrictEqual(exports[0], ['flavorfulTest', 'instance']); ok(files[name + '.js'].length < 11_000); }); @@ -46,9 +46,10 @@ export async function apiTest (fixtures) { js: true, }); strictEqual(imports.length, 2); - strictEqual(exports.length, 2); - deepStrictEqual(exports[0], ['exports', 'instance']); - deepStrictEqual(exports[1], ['testImports', 'function']); + strictEqual(exports.length, 3); + deepStrictEqual(exports[0], ['flavorfulTest', 'instance']); + deepStrictEqual(exports[1], ['test', 'instance']); + deepStrictEqual(exports[2], ['testImports', 'function']); const source = Buffer.from(files[name + '.js']).toString(); ok(source.includes('./wasi.js')); ok(source.includes('testwasi')); @@ -73,10 +74,7 @@ export async function apiTest (fixtures) { }); test('Wit & New', async () => { - const component = await readFile(`test/fixtures/components/flavorful.component.wasm`); - const wit = await componentWit(component); - - strictEqual(wit.slice(0, 19), 'interface imports {'); + const wit = await readFile(`test/fixtures/wit/flavorful.wit`, 'utf8'); const generatedComponent = await componentEmbed({ witSource: wit, @@ -99,7 +97,7 @@ export async function apiTest (fixtures) { tag: 'component', val: 4 }); - deepStrictEqual(meta[1].producers, [['processed-by', [['wit-component', '0.9.0'], ['dummy-gen', 'test']]], ['language', [['javascript', '']]]]); + deepStrictEqual(meta[1].producers, [['processed-by', [['wit-component', '0.11.0'], ['dummy-gen', 'test']]], ['language', [['javascript', '']]]]); }); test('Multi-file WIT', async () => { @@ -124,7 +122,7 @@ export async function apiTest (fixtures) { tag: 'component', val: 1 }); - deepStrictEqual(meta[1].producers, [['processed-by', [['wit-component', '0.9.0'], ['dummy-gen', 'test']]], ['language', [['javascript', '']]]]); + deepStrictEqual(meta[1].producers, [['processed-by', [['wit-component', '0.11.0'], ['dummy-gen', 'test']]], ['language', [['javascript', '']]]]); }); test('Component new adapt', async () => { diff --git a/test/cli.js b/test/cli.js index b9c31f371..0efb3770a 100644 --- a/test/cli.js +++ b/test/cli.js @@ -24,7 +24,7 @@ export async function cliTest (fixtures) { const { stderr } = await exec(jcoPath, 'transpile', `test/fixtures/components/${name}.component.wasm`, '--no-wasi-shim', '--name', name, '-o', outDir); strictEqual(stderr, ''); const source = await readFile(`${outDir}/${name}.js`); - ok(source.toString().includes('export { exports')); + ok(source.toString().includes('export { flavorfulTest')); } finally { await cleanup(); @@ -37,7 +37,7 @@ export async function cliTest (fixtures) { const { stderr } = await exec(jcoPath, 'transpile', `test/fixtures/components/${name}.component.wasm`, '--name', name, '--valid-lifting-optimization', '--tla-compat', '--optimize', '--minify', '--base64-cutoff=0', '-o', outDir); strictEqual(stderr, ''); const source = await readFile(`${outDir}/${name}.js`); - ok(source.toString().includes('as exports,')); + ok(source.toString().includes('as flavorfulTest,')); } finally { await cleanup(); @@ -100,16 +100,10 @@ export async function cliTest (fixtures) { try { const { stderr, stdout } = await exec(jcoPath, 'wit', `test/fixtures/components/flavorful.component.wasm`); strictEqual(stderr, ''); - ok(stdout.includes('world component {')); + ok(stdout.includes('world root {')); { - const { stderr, stdout } = await exec(jcoPath, 'wit', `test/fixtures/components/flavorful.component.wasm`, '-o', outFile); - strictEqual(stderr, ''); - strictEqual(stdout, ''); - } - - { - const { stderr, stdout } = await exec(jcoPath, 'embed', '--dummy', '--wit', outFile, '-m', 'language=javascript', '-m', 'processed-by=dummy-gen@test', '-o', outFile); + const { stderr, stdout } = await exec(jcoPath, 'embed', '--dummy', '--wit', 'test/fixtures/wit/flavorful.wit', '-m', 'language=javascript', '-m', 'processed-by=dummy-gen@test', '-o', outFile); strictEqual(stderr, ''); strictEqual(stdout, ''); } @@ -135,7 +129,7 @@ export async function cliTest (fixtures) { const meta = JSON.parse(stdout); deepStrictEqual(meta[0].metaType, { tag: 'component', val: 4 }); deepStrictEqual(meta[1].producers, [ - ['processed-by', [['wit-component', '0.9.0'], ['dummy-gen', 'test']]], + ['processed-by', [['wit-component', '0.11.0'], ['dummy-gen', 'test']]], ['language', [['javascript', '']]], ]); } @@ -151,7 +145,7 @@ export async function cliTest (fixtures) { 'new', 'test/fixtures/modules/exitcode.wasm', '--adapt', - 'wasi_snapshot_preview1=lib/wasi_preview1_component_adapter.reactor.wasm', + 'wasi_snapshot_preview1=lib/wasi_snapshot_preview1.reactor.wasm', '-o', outFile); strictEqual(stderr, ''); { @@ -187,7 +181,7 @@ export async function cliTest (fixtures) { } }); - test('Componentize', async () => { + test.skip('Componentize', async () => { try { const { stdout, stderr } = await exec(jcoPath, 'componentize', diff --git a/test/codegen.js b/test/codegen.js index f5ebcbb51..a5106e245 100644 --- a/test/codegen.js +++ b/test/codegen.js @@ -43,7 +43,7 @@ export async function codegenTest (fixtures) { }); suite(`Typescript compilation`, () => { - // TypeScript tests _must_ run after codegen to complete successfully + // TypeScript tests _must_ run after codegen to complete successfully // This is due to type checking against generated bindings test('TypeScript Compilation', async () => { var { stderr } = await exec(tscPath, '-p', 'test/tsconfig.json'); diff --git a/test/fixtures/componentize/source.wit b/test/fixtures/componentize/source.wit index 362626a8f..6bfa11861 100644 --- a/test/fixtures/componentize/source.wit +++ b/test/fixtures/componentize/source.wit @@ -1,3 +1,5 @@ -default world test { +package local:test + +world test { export hello: func() -> string } \ No newline at end of file diff --git a/test/fixtures/components/dummy_proxy.component.wasm b/test/fixtures/components/dummy_proxy.component.wasm deleted file mode 100644 index 05f8a9a5a..000000000 Binary files a/test/fixtures/components/dummy_proxy.component.wasm and /dev/null differ diff --git a/test/fixtures/components/dummy_reactor.component.wasm b/test/fixtures/components/dummy_reactor.component.wasm deleted file mode 100644 index 1475791b2..000000000 Binary files a/test/fixtures/components/dummy_reactor.component.wasm and /dev/null differ diff --git a/test/fixtures/components/flavorful.component.wasm b/test/fixtures/components/flavorful.component.wasm index 71bc5bf8b..f7afa18d3 100644 Binary files a/test/fixtures/components/flavorful.component.wasm and b/test/fixtures/components/flavorful.component.wasm differ diff --git a/test/fixtures/components/lists.component.wasm b/test/fixtures/components/lists.component.wasm index 9c8c815f5..f2b96d49a 100644 Binary files a/test/fixtures/components/lists.component.wasm and b/test/fixtures/components/lists.component.wasm differ diff --git a/test/fixtures/components/many-arguments.component.wasm b/test/fixtures/components/many-arguments.component.wasm index c361feb5d..5446a15cc 100644 Binary files a/test/fixtures/components/many-arguments.component.wasm and b/test/fixtures/components/many-arguments.component.wasm differ diff --git a/test/fixtures/components/many_arguments.component.wasm b/test/fixtures/components/many_arguments.component.wasm deleted file mode 100644 index 3cbe40024..000000000 Binary files a/test/fixtures/components/many_arguments.component.wasm and /dev/null differ diff --git a/test/fixtures/components/numbers.component.wasm b/test/fixtures/components/numbers.component.wasm index 0152237dd..ebd269839 100644 Binary files a/test/fixtures/components/numbers.component.wasm and b/test/fixtures/components/numbers.component.wasm differ diff --git a/test/fixtures/components/records.component.wasm b/test/fixtures/components/records.component.wasm index 5d3089e8b..129b840f3 100644 Binary files a/test/fixtures/components/records.component.wasm and b/test/fixtures/components/records.component.wasm differ diff --git a/test/fixtures/components/smoke.component.wasm b/test/fixtures/components/smoke.component.wasm index 268d8539f..c41358244 100644 Binary files a/test/fixtures/components/smoke.component.wasm and b/test/fixtures/components/smoke.component.wasm differ diff --git a/test/fixtures/components/strings.component.wasm b/test/fixtures/components/strings.component.wasm index 87fee853b..78975ccf9 100644 Binary files a/test/fixtures/components/strings.component.wasm and b/test/fixtures/components/strings.component.wasm differ diff --git a/test/fixtures/components/variants.component.wasm b/test/fixtures/components/variants.component.wasm index a7263ba8f..a20995599 100644 Binary files a/test/fixtures/components/variants.component.wasm and b/test/fixtures/components/variants.component.wasm differ diff --git a/test/fixtures/wit/flavorful.wit b/test/fixtures/wit/flavorful.wit new file mode 100644 index 000000000..5d601a6ed --- /dev/null +++ b/test/fixtures/wit/flavorful.wit @@ -0,0 +1,43 @@ +package test:flavorful + +interface test { + record list-in-record1 { a: string } + record list-in-record2 { a: string } + record list-in-record3 { a: string } + record list-in-record4 { a: string } + type list-in-alias = list-in-record4 + + f-list-in-record1: func(a: list-in-record1) + f-list-in-record2: func() -> list-in-record2 + f-list-in-record3: func(a: list-in-record3) -> list-in-record3 + f-list-in-record4: func(a: list-in-alias) -> list-in-alias + + type list-in-variant1-v1 = option + type list-in-variant1-v2 = result<_, string> + union list-in-variant1-v3 { string, float32 } + f-list-in-variant1: func(a: list-in-variant1-v1, b: list-in-variant1-v2, c: list-in-variant1-v3) + + type list-in-variant2 = option + f-list-in-variant2: func() -> list-in-variant2 + + type list-in-variant3 = option + f-list-in-variant3: func(a: list-in-variant3) -> list-in-variant3 + + enum my-errno { success, a, b } + errno-result: func() -> result<_, my-errno> + + type list-typedef = string + type list-typedef2 = list + type list-typedef3 = list + list-typedefs: func(a: list-typedef, c: list-typedef3) -> (a: list-typedef2, b: list-typedef3) + + list-of-variants: func(a: list, b: list, c: list) + -> (a: list, b: list, c: list) +} + +world flavorful { + import test + export test + + export test-imports: func() +} diff --git a/test/fixtures/wit/wasi/command-extended.wit b/test/fixtures/wit/wasi/command-extended.wit new file mode 100644 index 000000000..2beb119da --- /dev/null +++ b/test/fixtures/wit/wasi/command-extended.wit @@ -0,0 +1,36 @@ +package wasi:preview + +world command-extended { + import wasi:clocks/wall-clock + import wasi:clocks/monotonic-clock + import wasi:clocks/timezone + import wasi:filesystem/filesystem + import wasi:sockets/instance-network + import wasi:sockets/ip-name-lookup + import wasi:sockets/network + import wasi:sockets/tcp-create-socket + import wasi:sockets/tcp + import wasi:sockets/udp-create-socket + import wasi:sockets/udp + import wasi:random/random + import wasi:random/insecure + import wasi:random/insecure-seed + import wasi:poll/poll + import wasi:io/streams + import wasi:cli-base/environment + import wasi:cli-base/preopens + import wasi:cli-base/exit + import wasi:cli-base/stdin + import wasi:cli-base/stdout + import wasi:cli-base/stderr + + // We should replace all others with `include self.command` + // as soon as the unioning of worlds is available: + // https://github.com/WebAssembly/component-model/issues/169 + import wasi:logging/handler + import wasi:http/outgoing-handler + + export run: func( + args: list, + ) -> result +} diff --git a/test/fixtures/wit/wasi/command.wit b/test/fixtures/wit/wasi/command.wit new file mode 100644 index 000000000..62608a05c --- /dev/null +++ b/test/fixtures/wit/wasi/command.wit @@ -0,0 +1,26 @@ +world command { + import wasi:clocks/wall-clock + import wasi:clocks/monotonic-clock + import wasi:clocks/timezone + import wasi:filesystem/filesystem + import wasi:sockets/instance-network + import wasi:sockets/ip-name-lookup + import wasi:sockets/network + import wasi:sockets/tcp-create-socket + import wasi:sockets/tcp + import wasi:sockets/udp-create-socket + import wasi:sockets/udp + import wasi:random/random + import wasi:random/insecure + import wasi:random/insecure-seed + import wasi:poll/poll + import wasi:io/streams + import wasi:cli-base/environment + import wasi:cli-base/preopens + import wasi:cli-base/exit + import wasi:cli-base/stdin + import wasi:cli-base/stdout + import wasi:cli-base/stderr + + export run: func() -> result +} diff --git a/test/fixtures/wit/wasi/deps/clocks/monotonic-clock.wit b/test/fixtures/wit/wasi/deps/clocks/monotonic-clock.wit new file mode 100644 index 000000000..50eb4de11 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/clocks/monotonic-clock.wit @@ -0,0 +1,34 @@ +package wasi:clocks + +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +/// +/// It is intended for measuring elapsed time. +interface monotonic-clock { + use wasi:poll/poll.{pollable} + + /// A timestamp in nanoseconds. + type instant = u64 + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + now: func() -> instant + + /// Query the resolution of the clock. + resolution: func() -> instant + + /// Create a `pollable` which will resolve once the specified time has been + /// reached. + subscribe: func( + when: instant, + absolute: bool + ) -> pollable +} diff --git a/test/fixtures/wit/wasi/deps/clocks/timezone.wit b/test/fixtures/wit/wasi/deps/clocks/timezone.wit new file mode 100644 index 000000000..2b6855668 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/clocks/timezone.wit @@ -0,0 +1,63 @@ +package wasi:clocks + +interface timezone { + use wall-clock.{datetime} + + /// A timezone. + /// + /// In timezones that recognize daylight saving time, also known as daylight + /// time and summer time, the information returned from the functions varies + /// over time to reflect these adjustments. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type timezone = u32 + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + display: func(this: timezone, when: datetime) -> timezone-display + + /// The same as `display`, but only return the UTC offset. + utc-offset: func(this: timezone, when: datetime) -> s32 + + /// Dispose of the specified input-stream, after which it may no longer + /// be used. + drop-timezone: func(this: timezone) + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/test/fixtures/wit/wasi/deps/clocks/wall-clock.wit b/test/fixtures/wit/wasi/deps/clocks/wall-clock.wit new file mode 100644 index 000000000..6137724f6 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/clocks/wall-clock.wit @@ -0,0 +1,43 @@ +package wasi:clocks + +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + now: func() -> datetime + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + resolution: func() -> datetime +} diff --git a/test/fixtures/wit/wasi/deps/filesystem/filesystem.wit b/test/fixtures/wit/wasi/deps/filesystem/filesystem.wit new file mode 100644 index 000000000..930f37567 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/filesystem/filesystem.wit @@ -0,0 +1,782 @@ +package wasi:filesystem + +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +interface filesystem { + use wasi:io/streams.{input-stream, output-stream} + use wasi:clocks/wall-clock.{datetime} + + /// File size or length of a region within a file. + type filesize = u64 + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrety + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + record descriptor-stat { + /// Device ID of device containing the file. + device: device, + /// File serial number. + inode: inode, + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + data-access-timestamp: datetime, + /// Last data modification timestamp. + data-modification-timestamp: datetime, + /// Last file status change timestamp. + status-change-timestamp: datetime, + } + + /// Flags determining the method of how paths are resolved. + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Permissions mode used by `open-at`, `change-file-permissions-at`, and + /// similar. + flags modes { + /// True if the resource is considered readable by the containing + /// filesystem. + readable, + /// True if the resource is considered writable by the containing + /// filesystem. + writable, + /// True if the resource is considered executable by the containing + /// filesystem. This does not apply to directories. + executable, + } + + /// Access type used by `access-at`. + variant access-type { + /// Test for readability, writeability, or executability. + access(modes), + + /// Test whether the path exists. + exists, + } + + /// Number of hard links to an inode. + type link-count = u64 + + /// Identifier for a device containing a file system. Can be used in + /// combination with `inode` to uniquely identify a file or directory in + /// the filesystem. + type device = u64 + + /// Filesystem object serial number that is unique within its file system. + type inode = u64 + + /// When setting a timestamp, this gives the value to set it to. + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The serial number of the object referred to by this directory entry. + /// May be none if the inode value is not known. + /// + /// When this is none, libc implementations might do an extra `stat-at` + /// call to retrieve the inode number to fill their `d_ino` fields, so + /// implementations which can set this to a non-none value should do so. + inode: option, + + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type descriptor = u32 + + /// Return a stream for reading from a file. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `wasi:io/streams.read`, which is similar to `read` in POSIX. + read-via-stream: func( + this: descriptor, + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result + + /// Return a stream for writing to a file. + /// + /// Note: This allows using `wasi:io/streams.write`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + this: descriptor, + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result + + /// Return a stream for appending to a file. + /// + /// Note: This allows using `wasi:io/streams.write`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func( + this: descriptor, + ) -> result + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + this: descriptor, + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code> + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func(this: descriptor) -> result<_, error-code> + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func(this: descriptor) -> result + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func(this: descriptor) -> result + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(this: descriptor, size: filesize) -> result<_, error-code> + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + this: descriptor, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + this: descriptor, + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code> + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + this: descriptor, + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func( + this: descriptor + ) -> result + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func(this: descriptor) -> result<_, error-code> + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + this: descriptor, + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code> + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func(this: descriptor) -> result + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: descriptor, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code> + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + /// Permissions to use when creating a new file. + modes: modes + ) -> result + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + this: descriptor, + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + this: descriptor, + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code> + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + this: descriptor, + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: descriptor, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code> + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + this: descriptor, + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code> + + /// Check accessibility of a filesystem path. + /// + /// Check whether the given filesystem path names an object which is + /// readable, writable, or executable, or whether it exists. + /// + /// This does not a guarantee that subsequent accesses will succeed, as + /// filesystem permissions may be modified asynchronously by external + /// entities. + /// + /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. + access-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to check. + path: string, + /// The type of check to perform. + %type: access-type + ) -> result<_, error-code> + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + this: descriptor, + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code> + + /// Change the permissions of a filesystem object that is not a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-file-permissions-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the filesystem object. + modes: modes, + ) -> result<_, error-code> + + /// Change the permissions of a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" + /// flag. `read` on a directory implies readability and searchability, and + /// `execute` is not valid for directories. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-directory-permissions-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the directory. + modes: modes, + ) -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. + lock-shared: func(this: descriptor) -> result<_, error-code> + + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. + lock-exclusive: func(this: descriptor) -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. + try-lock-shared: func(this: descriptor) -> result<_, error-code> + + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. + try-lock-exclusive: func(this: descriptor) -> result<_, error-code> + + /// Release a shared or exclusive lock on an open file. + /// + /// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. + unlock: func(this: descriptor) -> result<_, error-code> + + /// Dispose of the specified `descriptor`, after which it may no longer + /// be used. + drop-descriptor: func(this: descriptor) + + /// A stream of directory entries. + /// + /// This [represents a stream of `dir-entry`](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Streams). + type directory-entry-stream = u32 + + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func( + this: directory-entry-stream + ) -> result, error-code> + + /// Dispose of the specified `directory-entry-stream`, after which it may no longer + /// be used. + drop-directory-entry-stream: func(this: directory-entry-stream) +} diff --git a/test/fixtures/wit/wasi/deps/http/incoming-handler.wit b/test/fixtures/wit/wasi/deps/http/incoming-handler.wit new file mode 100644 index 000000000..d0e270465 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/http/incoming-handler.wit @@ -0,0 +1,24 @@ +// The `wasi:http/incoming-handler` interface is meant to be exported by +// components and called by the host in response to a new incoming HTTP +// response. +// +// NOTE: in Preview3, this interface will be merged with +// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface +// that takes a `request` parameter and returns a `response` result. +// +interface incoming-handler { + use types.{incoming-request, response-outparam} + + // The `handle` function takes an outparam instead of returning its response + // so that the component may stream its response while streaming any other + // request or response bodies. The callee MUST write a response to the + // `response-out` and then finish the response before returning. The `handle` + // function is allowed to continue execution after finishing the response's + // output stream. While this post-response execution is taken off the + // critical path, since there is no return value, there is no way to report + // its success or failure. + handle: func( + request: incoming-request, + response-out: response-outparam + ) +} diff --git a/test/fixtures/wit/wasi/deps/http/outgoing-handler.wit b/test/fixtures/wit/wasi/deps/http/outgoing-handler.wit new file mode 100644 index 000000000..06c8e469f --- /dev/null +++ b/test/fixtures/wit/wasi/deps/http/outgoing-handler.wit @@ -0,0 +1,18 @@ +// The `wasi:http/outgoing-handler` interface is meant to be imported by +// components and implemented by the host. +// +// NOTE: in Preview3, this interface will be merged with +// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface +// that takes a `request` parameter and returns a `response` result. +// +interface outgoing-handler { + use types.{outgoing-request, request-options, future-incoming-response} + + // The parameter and result types of the `handle` function allow the caller + // to concurrently stream the bodies of the outgoing request and the incoming + // response. + handle: func( + request: outgoing-request, + options: option + ) -> future-incoming-response +} diff --git a/test/fixtures/wit/wasi/deps/http/types.wit b/test/fixtures/wit/wasi/deps/http/types.wit new file mode 100644 index 000000000..ee4227f42 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/http/types.wit @@ -0,0 +1,159 @@ +package wasi:http + +// The `wasi:http/types` interface is meant to be imported by components to +// define the HTTP resource types and operations used by the component's +// imported and exported interfaces. +interface types { + use wasi:io/streams.{input-stream, output-stream} + use wasi:poll/poll.{pollable} + + // This type corresponds to HTTP standard Methods. + variant method { + get, + head, + post, + put, + delete, + connect, + options, + trace, + patch, + other(string) + } + + // This type corresponds to HTTP standard Related Schemes. + variant scheme { + HTTP, + HTTPS, + other(string) + } + + // TODO: perhaps better align with HTTP semantics? + // This type enumerates the different kinds of errors that may occur when + // initially returning a response. + variant error { + invalid-url(string), + timeout-error(string), + protocol-error(string), + unexpected-error(string) + } + + // This following block defines the `fields` resource which corresponds to + // HTTP standard Fields. Soon, when resource types are added, the `type + // fields = u32` type alias can be replaced by a proper `resource fields` + // definition containing all the functions using the method syntactic sugar. + type fields = u32 + drop-fields: func(fields: fields) + new-fields: func(entries: list>) -> fields + fields-get: func(fields: fields, name: string) -> list + fields-set: func(fields: fields, name: string, value: list) + fields-delete: func(fields: fields, name: string) + fields-append: func(fields: fields, name: string, value: string) + fields-entries: func(fields: fields) -> list> + fields-clone: func(fields: fields) -> fields + + type headers = fields + type trailers = fields + + // The following block defines stream types which corresponds to the HTTP + // standard Contents and Trailers. With Preview3, all of these fields can be + // replaced by a stream>. In the interim, we need to + // build on separate resource types defined by `wasi:io/streams`. The + // `finish-` functions emulate the stream's result value and MUST be called + // exactly once after the final read/write from/to the stream before dropping + // the stream. + type incoming-stream = input-stream + type outgoing-stream = output-stream + finish-incoming-stream: func(s: incoming-stream) -> option + finish-outgoing-stream: func(s: outgoing-stream, trailers: option) + + // The following block defines the `incoming-request` and `outgoing-request` + // resource types that correspond to HTTP standard Requests. Soon, when + // resource types are added, the `u32` type aliases can be replaced by + // proper `resource` type definitions containing all the functions as + // methods. Later, Preview2 will allow both types to be merged together into + // a single `request` type (that uses the single `stream` type mentioned + // above). The `consume` and `write` methods may only be called once (and + // return failure thereafter). + type incoming-request = u32 + type outgoing-request = u32 + drop-incoming-request: func(request: incoming-request) + drop-outgoing-request: func(request: outgoing-request) + incoming-request-method: func(request: incoming-request) -> method + incoming-request-path: func(request: incoming-request) -> string + incoming-request-query: func(request: incoming-request) -> string + incoming-request-scheme: func(request: incoming-request) -> option + incoming-request-authority: func(request: incoming-request) -> string + incoming-request-headers: func(request: incoming-request) -> headers + incoming-request-consume: func(request: incoming-request) -> result + new-outgoing-request: func( + method: method, + path: string, + query: string, + scheme: option, + authority: string, + headers: headers + ) -> outgoing-request + outgoing-request-write: func(request: outgoing-request) -> result + + // Additional optional parameters that can be set when making a request. + record request-options { + // The following timeouts are specific to the HTTP protocol and work + // independently of the overall timeouts passed to `io.poll.poll-oneoff`. + + // The timeout for the initial connect. + connect-timeout-ms: option, + + // The timeout for receiving the first byte of the response body. + first-byte-timeout-ms: option, + + // The timeout for receiving the next chunk of bytes in the response body + // stream. + between-bytes-timeout-ms: option + } + + // The following block defines a special resource type used by the + // `wasi:http/incoming-handler` interface. When resource types are added, this + // block can be replaced by a proper `resource response-outparam { ... }` + // definition. Later, with Preview3, the need for an outparam goes away entirely + // (the `wasi:http/handler` interface used for both incoming and outgoing can + // simply return a `stream`). + type response-outparam = u32 + drop-response-outparam: func(response: response-outparam) + set-response-outparam: func(response: result) -> result + + // This type corresponds to the HTTP standard Status Code. + type status-code = u16 + + // The following block defines the `incoming-response` and `outgoing-response` + // resource types that correspond to HTTP standard Responses. Soon, when + // resource types are added, the `u32` type aliases can be replaced by proper + // `resource` type definitions containing all the functions as methods. Later, + // Preview2 will allow both types to be merged together into a single `response` + // type (that uses the single `stream` type mentioned above). The `consume` and + // `write` methods may only be called once (and return failure thereafter). + type incoming-response = u32 + type outgoing-response = u32 + drop-incoming-response: func(response: incoming-response) + drop-outgoing-response: func(response: outgoing-response) + incoming-response-status: func(response: incoming-response) -> status-code + incoming-response-headers: func(response: incoming-response) -> headers + incoming-response-consume: func(response: incoming-response) -> result + new-outgoing-response: func( + status-code: status-code, + headers: headers + ) -> outgoing-response + outgoing-response-write: func(response: outgoing-response) -> result + + // The following block defines a special resource type used by the + // `wasi:http/outgoing-handler` interface to emulate + // `future>` in advance of Preview3. Given a + // `future-incoming-response`, the client can call the non-blocking `get` + // method to get the result if it is available. If the result is not available, + // the client can call `listen` to get a `pollable` that can be passed to + // `io.poll.poll-oneoff`. + type future-incoming-response = u32 + drop-future-incoming-response: func(f: future-incoming-response) + future-incoming-response-get: func(f: future-incoming-response) -> option> + listen-to-future-incoming-response: func(f: future-incoming-response) -> pollable +} diff --git a/test/fixtures/wit/wasi/deps/io/streams.wit b/test/fixtures/wit/wasi/deps/io/streams.wit new file mode 100644 index 000000000..008e36cf5 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/io/streams.wit @@ -0,0 +1,215 @@ +package wasi:io + +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +interface streams { + use wasi:poll/poll.{pollable} + + /// An error type returned from a stream operation. Currently this + /// doesn't provide any additional information. + record stream-error {} + + /// An input bytestream. In the future, this will be replaced by handle + /// types. + /// + /// This conceptually represents a `stream`. It's temporary + /// scaffolding until component-model's async features are ready. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe-to-input-stream` function to obtain a `pollable` which + /// can be polled for using `wasi_poll`. + /// + /// And at present, it is a `u32` instead of being an actual handle, until + /// the wit-bindgen implementation of handles and resources is ready. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type input-stream = u32 + + /// Read bytes from a stream. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// stream was reached. The returned list will contain up to `len` bytes; it + /// may return fewer than requested, but not more. + /// + /// Once a stream has reached the end, subsequent calls to read or + /// `skip` will always report end-of-stream rather than producing more + /// data. + /// + /// If `len` is 0, it represents a request to read 0 bytes, which should + /// always succeed, assuming the stream hasn't reached its end yet, and + /// return an empty list. + /// + /// The len here is a `u64`, but some callees may not be able to allocate + /// a buffer as large as that would imply. + /// FIXME: describe what happens if allocation fails. + read: func( + this: input-stream, + /// The maximum number of bytes to read + len: u64 + ) -> result, bool>, stream-error> + + /// Read bytes from a stream, with blocking. + /// + /// This is similar to `read`, except that it blocks until at least one + /// byte can be read. + blocking-read: func( + this: input-stream, + /// The maximum number of bytes to read + len: u64 + ) -> result, bool>, stream-error> + + /// Skip bytes from a stream. + /// + /// This is similar to the `read` function, but avoids copying the + /// bytes into the instance. + /// + /// Once a stream has reached the end, subsequent calls to read or + /// `skip` will always report end-of-stream rather than producing more + /// data. + /// + /// This function returns the number of bytes skipped, along with a bool + /// indicating whether the end of the stream was reached. The returned + /// value will be at most `len`; it may be less. + skip: func( + this: input-stream, + /// The maximum number of bytes to skip. + len: u64, + ) -> result, stream-error> + + /// Skip bytes from a stream, with blocking. + /// + /// This is similar to `skip`, except that it blocks until at least one + /// byte can be consumed. + blocking-skip: func( + this: input-stream, + /// The maximum number of bytes to skip. + len: u64, + ) -> result, stream-error> + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + subscribe-to-input-stream: func(this: input-stream) -> pollable + + /// Dispose of the specified `input-stream`, after which it may no longer + /// be used. + drop-input-stream: func(this: input-stream) + + /// An output bytestream. In the future, this will be replaced by handle + /// types. + /// + /// This conceptually represents a `stream`. It's temporary + /// scaffolding until component-model's async features are ready. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe-to-output-stream` function to obtain a + /// `pollable` which can be polled for using `wasi_poll`. + /// + /// And at present, it is a `u32` instead of being an actual handle, until + /// the wit-bindgen implementation of handles and resources is ready. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type output-stream = u32 + + /// Write bytes to a stream. + /// + /// This function returns a `u64` indicating the number of bytes from + /// `buf` that were written; it may be less than the full list. + write: func( + this: output-stream, + /// Data to write + buf: list + ) -> result + + /// Write bytes to a stream, with blocking. + /// + /// This is similar to `write`, except that it blocks until at least one + /// byte can be written. + blocking-write: func( + this: output-stream, + /// Data to write + buf: list + ) -> result + + /// Write multiple zero bytes to a stream. + /// + /// This function returns a `u64` indicating the number of zero bytes + /// that were written; it may be less than `len`. + write-zeroes: func( + this: output-stream, + /// The number of zero bytes to write + len: u64 + ) -> result + + /// Write multiple zero bytes to a stream, with blocking. + /// + /// This is similar to `write-zeroes`, except that it blocks until at least + /// one byte can be written. + blocking-write-zeroes: func( + this: output-stream, + /// The number of zero bytes to write + len: u64 + ) -> result + + /// Read from one stream and write to another. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + /// + /// Unlike other I/O functions, this function blocks until all the data + /// read from the input stream has been written to the output stream. + splice: func( + this: output-stream, + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result, stream-error> + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until at least + /// one byte can be read. + blocking-splice: func( + this: output-stream, + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result, stream-error> + + /// Forward the entire contents of an input stream to an output stream. + /// + /// This function repeatedly reads from the input stream and writes + /// the data to the output stream, until the end of the input stream + /// is reached, or an error is encountered. + /// + /// Unlike other I/O functions, this function blocks until the end + /// of the input stream is seen and all the data has been written to + /// the output stream. + /// + /// This function returns the number of bytes transferred. + forward: func( + this: output-stream, + /// The stream to read from + src: input-stream + ) -> result + + /// Create a `pollable` which will resolve once either the specified stream + /// is ready to accept bytes or the other end of the stream has been closed. + subscribe-to-output-stream: func(this: output-stream) -> pollable + + /// Dispose of the specified `output-stream`, after which it may no longer + /// be used. + drop-output-stream: func(this: output-stream) +} diff --git a/test/fixtures/wit/wasi/deps/logging/handler.wit b/test/fixtures/wit/wasi/deps/logging/handler.wit new file mode 100644 index 000000000..e6b077be8 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/logging/handler.wit @@ -0,0 +1,34 @@ +package wasi:logging + +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +interface handler { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + + /// Describes messages indicating hazardous situations. + warn, + + /// Describes messages indicating serious errors. + error, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string) +} diff --git a/test/fixtures/wit/wasi/deps/poll/poll.wit b/test/fixtures/wit/wasi/deps/poll/poll.wit new file mode 100644 index 000000000..cf5d8779c --- /dev/null +++ b/test/fixtures/wit/wasi/deps/poll/poll.wit @@ -0,0 +1,41 @@ +package wasi:poll + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// A "pollable" handle. + /// + /// This is conceptually represents a `stream<_, _>`, or in other words, + /// a stream that one can wait on, repeatedly, but which does not itself + /// produce any data. It's temporary scaffolding until component-model's + /// async features are ready. + /// + /// And at present, it is a `u32` instead of being an actual handle, until + /// the wit-bindgen implementation of handles and resources is ready. + /// + /// `pollable` lifetimes are not automatically managed. Users must ensure + /// that they do not outlive the resource they reference. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type pollable = u32 + + /// Dispose of the specified `pollable`, after which it may no longer + /// be used. + drop-pollable: func(this: pollable) + + /// Poll for completion on a set of pollables. + /// + /// The "oneoff" in the name refers to the fact that this function must do a + /// linear scan through the entire list of subscriptions, which may be + /// inefficient if the number is large and the same subscriptions are used + /// many times. In the future, this is expected to be obsoleted by the + /// component model async proposal, which will include a scalable waiting + /// facility. + /// + /// Note that the return type would ideally be `list`, but that would + /// be more difficult to polyfill given the current state of `wit-bindgen`. + /// See + /// for details. For now, we use zero to mean "not ready" and non-zero to + /// mean "ready". + poll-oneoff: func(in: list) -> list +} diff --git a/test/fixtures/wit/wasi/deps/random/insecure-seed.wit b/test/fixtures/wit/wasi/deps/random/insecure-seed.wit new file mode 100644 index 000000000..ff2ff65d0 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/random/insecure-seed.wit @@ -0,0 +1,24 @@ +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + insecure-seed: func() -> tuple +} diff --git a/test/fixtures/wit/wasi/deps/random/insecure.wit b/test/fixtures/wit/wasi/deps/random/insecure.wit new file mode 100644 index 000000000..ff0826822 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/random/insecure.wit @@ -0,0 +1,21 @@ +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + get-insecure-random-bytes: func(len: u64) -> list + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + get-insecure-random-u64: func() -> u64 +} diff --git a/test/fixtures/wit/wasi/deps/random/random.wit b/test/fixtures/wit/wasi/deps/random/random.wit new file mode 100644 index 000000000..f2bd6358c --- /dev/null +++ b/test/fixtures/wit/wasi/deps/random/random.wit @@ -0,0 +1,25 @@ +package wasi:random + +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface random { + /// Return `len` cryptographically-secure pseudo-random bytes. + /// + /// This function must produce data from an adequately seeded + /// cryptographically-secure pseudo-random number generator (CSPRNG), so it + /// must not block, from the perspective of the calling program, and the + /// returned data is always unpredictable. + /// + /// This function must always return fresh pseudo-random data. Deterministic + /// environments must omit this function, rather than implementing it with + /// deterministic data. + get-random-bytes: func(len: u64) -> list + + /// Return a cryptographically-secure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-random-bytes`, represented as a `u64`. + get-random-u64: func() -> u64 +} diff --git a/test/fixtures/wit/wasi/deps/sockets/instance-network.wit b/test/fixtures/wit/wasi/deps/sockets/instance-network.wit new file mode 100644 index 000000000..d911a29cc --- /dev/null +++ b/test/fixtures/wit/wasi/deps/sockets/instance-network.wit @@ -0,0 +1,9 @@ + +/// This interface provides a value-export of the default network handle.. +interface instance-network { + use network.{network} + + /// Get a handle to the default network. + instance-network: func() -> network + +} diff --git a/test/fixtures/wit/wasi/deps/sockets/ip-name-lookup.wit b/test/fixtures/wit/wasi/deps/sockets/ip-name-lookup.wit new file mode 100644 index 000000000..6c64b4617 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/sockets/ip-name-lookup.wit @@ -0,0 +1,69 @@ + +interface ip-name-lookup { + use wasi:poll/poll.{pollable} + use network.{network, error-code, ip-address, ip-address-family} + + + /// Resolve an internet host name to a list of IP addresses. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// # Parameters + /// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted + /// to ASCII using IDNA encoding. + /// - `address-family`: If provided, limit the results to addresses of this specific address family. + /// - `include-unavailable`: When set to true, this function will also return addresses of which the runtime + /// thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on + /// systems without an active IPv6 interface. Notes: + /// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. + /// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. + /// + /// This function never blocks. It either immediately fails or immediately returns successfully with a `resolve-address-stream` + /// that can be used to (asynchronously) fetch the results. + /// + /// At the moment, the stream never completes successfully with 0 items. Ie. the first call + /// to `resolve-next-address` never returns `ok(none)`. This may change in the future. + /// + /// # Typical errors + /// - `invalid-name`: `name` is a syntactically invalid domain name. + /// - `invalid-name`: `name` is an IP address. + /// - `address-family-not-supported`: The specified `address-family` is not supported. (EAI_FAMILY) + /// + /// # References: + /// - + /// - + /// - + /// - + resolve-addresses: func(network: network, name: string, address-family: option, include-unavailable: bool) -> result + + + + type resolve-address-stream = u32 + + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// After which, you should release the stream with `drop-resolve-address-stream`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func(this: resolve-address-stream) -> result, error-code> + + /// Dispose of the specified `resolve-address-stream`, after which it may no longer be used. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-resolve-address-stream: func(this: resolve-address-stream) + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func(this: resolve-address-stream) -> pollable +} diff --git a/test/fixtures/wit/wasi/deps/sockets/network.wit b/test/fixtures/wit/wasi/deps/sockets/network.wit new file mode 100644 index 000000000..c370214ce --- /dev/null +++ b/test/fixtures/wit/wasi/deps/sockets/network.wit @@ -0,0 +1,187 @@ +package wasi:sockets + +interface network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + /// + /// FYI, In the future this will be replaced by handle types. + type network = u32 + + /// Dispose of the specified `network`, after which it may no longer be used. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-network: func(this: network) + + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + enum error-code { + // ### GENERAL ERRORS ### + + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + + /// The operation timed out before it could finish completely. + timeout, + + /// This operation is incompatible with another asynchronous operation that is already in progress. + concurrency-conflict, + + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + + + // ### IP ERRORS ### + + /// The specified address-family is not supported. + address-family-not-supported, + + /// An IPv4 address was passed to an IPv6 resource, or vice versa. + address-family-mismatch, + + /// The socket address is not a valid remote address. E.g. the IP address is set to INADDR_ANY, or the port is set to 0. + invalid-remote-address, + + /// The operation is only supported on IPv4 resources. + ipv4-only-operation, + + /// The operation is only supported on IPv6 resources. + ipv6-only-operation, + + + + // ### TCP & UDP SOCKET ERRORS ### + + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + + /// The socket is already attached to another network. + already-attached, + + /// The socket is already bound. + already-bound, + + /// The socket is already in the Connection state. + already-connected, + + /// The socket is not bound to any local address. + not-bound, + + /// The socket is not in the Connection state. + not-connected, + + /// A bind operation failed because the provided address is not an address that the `network` can bind to. + address-not-bindable, + + /// A bind operation failed because the provided address is already in use. + address-in-use, + + /// A bind operation failed because there are no ephemeral ports available. + ephemeral-ports-exhausted, + + /// The remote address is not reachable + remote-unreachable, + + + // ### TCP SOCKET ERRORS ### + + /// The socket is already in the Listener state. + already-listening, + + /// The socket is already in the Listener state. + not-listening, + + /// The connection was forcefully rejected + connection-refused, + + /// The connection was reset. + connection-reset, + + + // ### UDP SOCKET ERRORS ### + datagram-too-large, + + + // ### NAME LOOKUP ERRORS ### + + /// The provided name is a syntactically invalid domain name. + invalid-name, + + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + permanent-resolver-failure, + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple + type ipv6-address = tuple + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + port: u16, // sin_port + address: ipv4-address, // sin_addr + } + + record ipv6-socket-address { + port: u16, // sin6_port + flow-info: u32, // sin6_flowinfo + address: ipv6-address, // sin6_addr + scope-id: u32, // sin6_scope_id + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + +} diff --git a/test/fixtures/wit/wasi/deps/sockets/tcp-create-socket.wit b/test/fixtures/wit/wasi/deps/sockets/tcp-create-socket.wit new file mode 100644 index 000000000..f467d2856 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/sockets/tcp-create-socket.wit @@ -0,0 +1,27 @@ + +interface tcp-create-socket { + use network.{network, error-code, ip-address-family} + use tcp.{tcp-socket} + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The host does not support TCP sockets. (EOPNOTSUPP) + /// - `address-family-not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + create-tcp-socket: func(address-family: ip-address-family) -> result +} diff --git a/test/fixtures/wit/wasi/deps/sockets/tcp.wit b/test/fixtures/wit/wasi/deps/sockets/tcp.wit new file mode 100644 index 000000000..7ed46a690 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/sockets/tcp.wit @@ -0,0 +1,255 @@ + +interface tcp { + use wasi:io/streams.{input-stream, output-stream} + use wasi:poll/poll.{pollable} + use network.{network, error-code, ip-socket-address, ip-address-family} + + /// A TCP socket handle. + type tcp-socket = u32 + + + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will + /// implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + /// - `already-bound`: The socket is already bound. (EINVAL) + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(this: tcp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> + finish-bind: func(this: tcp-socket) -> result<_, error-code> + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the Connection state + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `already-attached`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `already-connected`: The socket is already in the Connection state. (EISCONN) + /// - `already-listening`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(this: tcp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> + finish-connect: func(this: tcp-socket) -> result, error-code> + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `already-attached`: The socket is already attached to a different network. The `network` passed to `listen` must be identical to the one passed to `bind`. + /// - `already-connected`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) + /// - `already-listening`: The socket is already in the Listener state. + /// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EINVAL on BSD) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A `listen` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func(this: tcp-socket, network: network) -> result<_, error-code> + finish-listen: func(this: tcp-socket) -> result<_, error-code> + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `not-listening`: Socket is not in the Listener state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// + /// Host implementations must skip over transient errors returned by the native accept syscall. + /// + /// # References + /// - + /// - + /// - + /// - + accept: func(this: tcp-socket) -> result, error-code> + + /// Get the bound local address. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func(this: tcp-socket) -> result + + /// Get the bound remote address. + /// + /// # Typical errors + /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func(this: tcp-socket) -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func(this: tcp-socket) -> ip-address-family + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + /// - `already-bound`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + ipv6-only: func(this: tcp-socket) -> result + set-ipv6-only: func(this: tcp-socket, value: bool) -> result<_, error-code> + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + set-listen-backlog-size: func(this: tcp-socket, value: u64) -> result<_, error-code> + + /// Equivalent to the SO_KEEPALIVE socket option. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + keep-alive: func(this: tcp-socket) -> result + set-keep-alive: func(this: tcp-socket, value: bool) -> result<_, error-code> + + /// Equivalent to the TCP_NODELAY socket option. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + no-delay: func(this: tcp-socket) -> result + set-no-delay: func(this: tcp-socket, value: bool) -> result<_, error-code> + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `already-listening`: (set) The socket is already in the Listener state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + unicast-hop-limit: func(this: tcp-socket) -> result + set-unicast-hop-limit: func(this: tcp-socket, value: u8) -> result<_, error-code> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `already-connected`: (set) The socket is already in the Connection state. + /// - `already-listening`: (set) The socket is already in the Listener state. + /// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) + receive-buffer-size: func(this: tcp-socket) -> result + set-receive-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> + send-buffer-size: func(this: tcp-socket) -> result + set-send-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func(this: tcp-socket) -> pollable + + /// Initiate a graceful shutdown. + /// + /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. + /// Any data still in the receive queue at time of calling `shutdown` will be discarded. + /// - send: the socket is not expecting to send any more data to the peer. All subsequent write + /// operations on the `output-stream` associated with this socket will return an error. + /// - both: same effect as receive & send combined. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `not-connected`: The socket is not in the Connection state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(this: tcp-socket, shutdown-type: shutdown-type) -> result<_, error-code> + + /// Dispose of the specified `tcp-socket`, after which it may no longer be used. + /// + /// Similar to the POSIX `close` function. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-tcp-socket: func(this: tcp-socket) +} diff --git a/test/fixtures/wit/wasi/deps/sockets/udp-create-socket.wit b/test/fixtures/wit/wasi/deps/sockets/udp-create-socket.wit new file mode 100644 index 000000000..1cfbd7f0b --- /dev/null +++ b/test/fixtures/wit/wasi/deps/sockets/udp-create-socket.wit @@ -0,0 +1,27 @@ + +interface udp-create-socket { + use network.{network, error-code, ip-address-family} + use udp.{udp-socket} + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The host does not support UDP sockets. (EOPNOTSUPP) + /// - `address-family-not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + create-udp-socket: func(address-family: ip-address-family) -> result +} diff --git a/test/fixtures/wit/wasi/deps/sockets/udp.wit b/test/fixtures/wit/wasi/deps/sockets/udp.wit new file mode 100644 index 000000000..9dd4573bd --- /dev/null +++ b/test/fixtures/wit/wasi/deps/sockets/udp.wit @@ -0,0 +1,211 @@ + +interface udp { + use wasi:poll/poll.{pollable} + use network.{network, error-code, ip-socket-address, ip-address-family} + + + /// A UDP socket handle. + type udp-socket = u32 + + + record datagram { + data: list, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + remote-address: ip-socket-address, + + /// Possible future additions: + /// local-address: ip-socket-address, // IP_PKTINFO / IP_RECVDSTADDR / IPV6_PKTINFO + /// local-interface: u32, // IP_PKTINFO / IP_RECVIF + /// ttl: u8, // IP_RECVTTL + /// dscp: u6, // IP_RECVTOS + /// ecn: u2, // IP_RECVTOS + } + + + + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) + /// - `already-bound`: The socket is already bound. (EINVAL) + /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(this: udp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> + finish-bind: func(this: udp-socket) -> result<_, error-code> + + /// Set the destination address. + /// + /// The local-address is updated based on the best network path to `remote-address`. + /// + /// When a destination address is set: + /// - all receive operations will only return datagrams sent from the provided `remote-address`. + /// - the `send` function can only be used to send to this destination. + /// + /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `already-attached`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) + /// + /// # Typical `finish` errors + /// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(this: udp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> + finish-connect: func(this: udp-socket) -> result<_, error-code> + + /// Receive a message. + /// + /// Returns: + /// - The sender address of the datagram + /// - The number of bytes read. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. (EINVAL) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(this: udp-socket) -> result + + /// Send a message to a specific destination address. + /// + /// The remote address option is required. To send a message to the "connected" peer, + /// call `remote-address` to get their address. + /// + /// # Typical errors + /// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `already-connected`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) + /// - `not-bound`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(this: udp-socket, datagram: datagram) -> result<_, error-code> + + /// Get the current bound address. + /// + /// # Typical errors + /// - `not-bound`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func(this: udp-socket) -> result + + /// Get the address set with `connect`. + /// + /// # Typical errors + /// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func(this: udp-socket) -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func(this: udp-socket) -> ip-address-family + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. + /// - `already-bound`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + ipv6-only: func(this: udp-socket) -> result + set-ipv6-only: func(this: udp-socket, value: bool) -> result<_, error-code> + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + unicast-hop-limit: func(this: udp-socket) -> result + set-unicast-hop-limit: func(this: udp-socket, value: u8) -> result<_, error-code> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Fails when this socket is in the Listening state. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) + receive-buffer-size: func(this: udp-socket) -> result + set-receive-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> + send-buffer-size: func(this: udp-socket) -> result + set-send-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func(this: udp-socket) -> pollable + + /// Dispose of the specified `udp-socket`, after which it may no longer be used. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-udp-socket: func(this: udp-socket) +} diff --git a/test/fixtures/wit/wasi/deps/wasi-cli-base/environment.wit b/test/fixtures/wit/wasi/deps/wasi-cli-base/environment.wit new file mode 100644 index 000000000..4c97c85d1 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/wasi-cli-base/environment.wit @@ -0,0 +1,16 @@ +package wasi:cli-base + +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + get-environment: func() -> list> + + /// Get the POSIX-style arguments to the program. + get-arguments: func() -> list +} diff --git a/test/fixtures/wit/wasi/deps/wasi-cli-base/exit.wit b/test/fixtures/wit/wasi/deps/wasi-cli-base/exit.wit new file mode 100644 index 000000000..66835aa70 --- /dev/null +++ b/test/fixtures/wit/wasi/deps/wasi-cli-base/exit.wit @@ -0,0 +1,4 @@ +interface exit { + /// Exit the curerent instance and any linked instances. + exit: func(status: result) +} diff --git a/test/fixtures/wit/wasi/deps/wasi-cli-base/preopens.wit b/test/fixtures/wit/wasi/deps/wasi-cli-base/preopens.wit new file mode 100644 index 000000000..f268f6b0b --- /dev/null +++ b/test/fixtures/wit/wasi/deps/wasi-cli-base/preopens.wit @@ -0,0 +1,7 @@ +interface preopens { + use wasi:filesystem/filesystem.{descriptor} + use wasi:io/streams.{input-stream, output-stream} + + /// Return the set of of preopened directories, and their path. + get-directories: func() -> list> +} diff --git a/test/fixtures/wit/wasi/deps/wasi-cli-base/stdio.wit b/test/fixtures/wit/wasi/deps/wasi-cli-base/stdio.wit new file mode 100644 index 000000000..6c9d4a41a --- /dev/null +++ b/test/fixtures/wit/wasi/deps/wasi-cli-base/stdio.wit @@ -0,0 +1,17 @@ +interface stdin { + use wasi:io/streams.{input-stream} + + get-stdin: func() -> input-stream +} + +interface stdout { + use wasi:io/streams.{output-stream} + + get-stdout: func() -> output-stream +} + +interface stderr { + use wasi:io/streams.{output-stream} + + get-stderr: func() -> output-stream +} diff --git a/test/fixtures/wit/wasi/proxy.wit b/test/fixtures/wit/wasi/proxy.wit new file mode 100644 index 000000000..a616daa1c --- /dev/null +++ b/test/fixtures/wit/wasi/proxy.wit @@ -0,0 +1,9 @@ +world proxy { + import wasi:random/random + import wasi:random/insecure + import wasi:random/insecure-seed + import wasi:logging/handler + import wasi:http/outgoing-handler + + export wasi:http/incoming-handler +} diff --git a/test/fixtures/wit/wasi/reactor.wit b/test/fixtures/wit/wasi/reactor.wit new file mode 100644 index 000000000..4a262c551 --- /dev/null +++ b/test/fixtures/wit/wasi/reactor.wit @@ -0,0 +1,24 @@ +world reactor { + import wasi:clocks/wall-clock + import wasi:clocks/monotonic-clock + import wasi:clocks/timezone + import wasi:filesystem/filesystem + import wasi:sockets/instance-network + import wasi:sockets/ip-name-lookup + import wasi:sockets/network + import wasi:sockets/tcp-create-socket + import wasi:sockets/tcp + import wasi:sockets/udp-create-socket + import wasi:sockets/udp + import wasi:random/random + import wasi:poll/poll + import wasi:io/streams + import wasi:logging/handler + import wasi:http/outgoing-handler + import wasi:cli-base/environment + import wasi:cli-base/preopens + import wasi:cli-base/exit + import wasi:cli-base/stdin + import wasi:cli-base/stdout + import wasi:cli-base/stderr +} diff --git a/test/runtime/dummy_proxy.ts b/test/runtime/dummy_proxy.ts index 51c14e5c5..9730af0b1 100644 --- a/test/runtime/dummy_proxy.ts +++ b/test/runtime/dummy_proxy.ts @@ -1,7 +1,10 @@ // Flags: --instantiation import * as assert from "node:assert"; +// @ts-ignore import { importObject } from "@bytecodealliance/preview2-shim"; + +// @ts-ignore import { instantiate } from "../output/dummy_proxy/dummy_proxy.js"; import * as helpers from "./helpers.js"; diff --git a/test/runtime/flavorful.ts b/test/runtime/flavorful.ts index a8d633f06..538fab21f 100644 --- a/test/runtime/flavorful.ts +++ b/test/runtime/flavorful.ts @@ -1,76 +1,78 @@ -// Flags: --map testwasi=../helpers.js --map imports=../flavorful.js --js +// Flags: --map testwasi=../helpers.js --map test:flavorful=../flavorful.js --js // @ts-nocheck import * as assert from 'assert'; // Imports -export function fListInRecord1(x: any) {} -export function fListInRecord2() { return { a: 'list_in_record2' }; } -export function fListInRecord3(x: any) { - assert.strictEqual(x.a, 'list_in_record3 input'); - return { a: 'list_in_record3 output' }; -} -export function fListInRecord4(x: any) { - assert.strictEqual(x.a, 'input4'); - return { a: 'result4' }; -} -export function fListInVariant1(a: any, b: any, c: any) { - assert.strictEqual(a, 'foo'); - assert.deepStrictEqual(b, { tag: 'err', val: 'bar' }); - assert.deepStrictEqual(c, { tag: 0, val: 'baz' }); -} -export function fListInVariant2() { return 'list_in_variant2'; } -export function fListInVariant3(x: any) { - assert.strictEqual(x, 'input3'); - return 'output3'; -} let firstErr = true; -export function errnoResult() { - if (firstErr) { - firstErr = false; - throw 'b'; +export const flavorfulTest = { + fListInRecord1(x: any) {}, + fListInRecord2() { return { a: 'list_in_record2' }; }, + fListInRecord3(x: any) { + assert.strictEqual(x.a, 'list_in_record3 input'); + return { a: 'list_in_record3 output' }; + }, + fListInRecord4(x: any) { + assert.strictEqual(x.a, 'input4'); + return { a: 'result4' }; + }, + fListInVariant1(a: any, b: any, c: any) { + assert.strictEqual(a, 'foo'); + assert.deepStrictEqual(b, { tag: 'err', val: 'bar' }); + assert.deepStrictEqual(c, { tag: 0, val: 'baz' }); + }, + fListInVariant2() { return 'list_in_variant2'; }, + fListInVariant3(x: any) { + assert.strictEqual(x, 'input3'); + return 'output3'; + }, + errnoResult() { + if (firstErr) { + firstErr = false; + throw 'b'; + } + }, + listTypedefs(x: any, y: any) { + assert.strictEqual(x, 'typedef1'); + assert.deepStrictEqual(y, ['typedef2']); + return [(new TextEncoder).encode('typedef3'), ['typedef4']]; + }, + listOfVariants(bools: any, results: any, enums: any) { + assert.deepStrictEqual(bools, [true, false]); + assert.deepStrictEqual(results, [{ tag: 'ok', val: undefined }, { tag: 'err', val: undefined }]); + assert.deepStrictEqual(enums, ["success", "a"]); + return [ + [false, true], + [{ tag: 'err', val: undefined }, { tag: 'ok', val: undefined }], + ["a", "b"], + ]; } -} -export function listTypedefs(x: any, y: any) { - assert.strictEqual(x, 'typedef1'); - assert.deepStrictEqual(y, ['typedef2']); - return [(new TextEncoder).encode('typedef3'), ['typedef4']]; -} -export function listOfVariants(bools: any, results: any, enums: any) { - assert.deepStrictEqual(bools, [true, false]); - assert.deepStrictEqual(results, [{ tag: 'ok', val: undefined }, { tag: 'err', val: undefined }]); - assert.deepStrictEqual(enums, ["success", "a"]); - return [ - [false, true], - [{ tag: 'err', val: undefined }, { tag: 'ok', val: undefined }], - ["a", "b"], - ]; -} +}; export async function run () { const wasm = await import('../output/flavorful/flavorful.js'); wasm.testImports(); - wasm.exports.fListInRecord1({ a: "list_in_record1" }); - assert.deepStrictEqual(wasm.exports.fListInRecord2(), { a: "list_in_record2" }); + wasm.test.fListInRecord1({ a: "list_in_record1" }); + assert.deepStrictEqual(wasm.test.fListInRecord2(), { a: "list_in_record2" }); assert.deepStrictEqual( - wasm.exports.fListInRecord3({ a: "list_in_record3 input" }), + wasm.test.fListInRecord3({ a: "list_in_record3 input" }), { a: "list_in_record3 output" }, ); assert.deepStrictEqual( - wasm.exports.fListInRecord4({ a: "input4" }), + wasm.test.fListInRecord4({ a: "input4" }), { a: "result4" }, ); - wasm.exports.fListInVariant1("foo", { tag: 'err', val: 'bar' }, { tag: 0, val: 'baz' }); + wasm.test.fListInVariant1("foo", { tag: 'err', val: 'bar' }, { tag: 0, val: 'baz' }); - assert.deepStrictEqual(wasm.exports.fListInVariant2(), "list_in_variant2"); - assert.deepStrictEqual(wasm.exports.fListInVariant3("input3"), "output3"); + assert.deepStrictEqual(wasm.test.fListInVariant2(), "list_in_variant2"); + assert.deepStrictEqual(wasm.test.fListInVariant3("input3"), "output3"); try { - wasm.exports.errnoResult(); + wasm.test.errnoResult(); assert.ok(false); } catch (e: any) { @@ -79,7 +81,7 @@ export async function run () { assert.strictEqual(e.payload, 'b'); } - const [r1, r2] = wasm.exports.listTypedefs("typedef1", ["typedef2"]); + const [r1, r2] = wasm.test.listTypedefs("typedef1", ["typedef2"]); assert.deepStrictEqual(r1, (new TextEncoder()).encode('typedef3')); assert.deepStrictEqual(r2, ['typedef4']); } diff --git a/test/runtime/lists.ts b/test/runtime/lists.ts index 7820bac3c..ade4a44e6 100644 --- a/test/runtime/lists.ts +++ b/test/runtime/lists.ts @@ -9,122 +9,125 @@ import * as assert from 'assert'; async function run() { const wasm = await instantiate(helpers.loadWasm, { testwasi: helpers, - imports: { - emptyListParam(a) { - assert.deepStrictEqual(Array.from(a), []); - }, - emptyStringParam(a) { - assert.strictEqual(a, ''); - }, - emptyListResult() { - return new Uint8Array([]); - }, - emptyStringResult() { return ''; }, - listParam(a) { - assert.deepStrictEqual(Array.from(a), [1, 2, 3, 4]); - }, - listParam2(a) { - assert.strictEqual(a, 'foo'); - }, - listParam3(a) { - assert.deepStrictEqual(a, ['foo', 'bar', 'baz']); - }, - listParam4(a) { - assert.deepStrictEqual(a, [['foo', 'bar'], ['baz']]); - }, - listResult() { - return new Uint8Array([1, 2, 3, 4, 5]); - }, - listResult2() { return 'hello!'; }, - listResult3() { return ['hello,', 'world!']; }, - listRoundtrip(x) { return x; }, - stringRoundtrip(x) { return x; }, - - listMinmax8(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0); - assert.deepEqual(u[1], (1 << 8) - 1); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], -(1 << 7)); - assert.deepEqual(s[1], (1 << 7) - 1); - - return [u, s]; - }, - - listMinmax16(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0); - assert.deepEqual(u[1], (1 << 16) - 1); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], -(1 << 15)); - assert.deepEqual(s[1], (1 << 15) - 1); - - return [u, s]; - }, - - listMinmax32(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0); - assert.deepEqual(u[1], ~0 >>> 0); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], 1 << 31); - assert.deepEqual(s[1], ((1 << 31) - 1) >>> 0); - - return [u, s]; - }, - - listMinmax64(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0n); - assert.deepEqual(u[1], (2n ** 64n) - 1n); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], -(2n ** 63n)); - assert.deepEqual(s[1], (2n ** 63n) - 1n); - - return [u, s]; - }, - - listMinmaxFloat(f, d) { - assert.deepEqual(f.length, 4); - assert.deepEqual(f[0], -3.4028234663852886e+38); - assert.deepEqual(f[1], 3.4028234663852886e+38); - assert.deepEqual(f[2], Number.NEGATIVE_INFINITY); - assert.deepEqual(f[3], Number.POSITIVE_INFINITY); - - assert.deepEqual(d.length, 4); - assert.deepEqual(d[0], -Number.MAX_VALUE); - assert.deepEqual(d[1], Number.MAX_VALUE); - assert.deepEqual(d[2], Number.NEGATIVE_INFINITY); - assert.deepEqual(d[3], Number.POSITIVE_INFINITY); - - return [f, d]; - }, - }, + lists: { + listsTest: { + emptyListParam(a) { + assert.deepStrictEqual(Array.from(a), []); + }, + emptyStringParam(a) { + assert.strictEqual(a, ''); + }, + emptyListResult() { + return new Uint8Array([]); + }, + emptyStringResult() { return ''; }, + listParam(a) { + assert.deepStrictEqual(Array.from(a), [1, 2, 3, 4]); + }, + listParam2(a) { + assert.strictEqual(a, 'foo'); + }, + listParam3(a) { + assert.deepStrictEqual(a, ['foo', 'bar', 'baz']); + }, + listParam4(a) { + assert.deepStrictEqual(a, [['foo', 'bar'], ['baz']]); + }, + listResult() { + return new Uint8Array([1, 2, 3, 4, 5]); + }, + listResult2() { return 'hello!'; }, + listResult3() { return ['hello,', 'world!']; }, + listRoundtrip(x) { return x; }, + stringRoundtrip(x) { return x; }, + + listMinmax8(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0); + assert.deepEqual(u[1], (1 << 8) - 1); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], -(1 << 7)); + assert.deepEqual(s[1], (1 << 7) - 1); + + return [u, s]; + }, + + listMinmax16(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0); + assert.deepEqual(u[1], (1 << 16) - 1); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], -(1 << 15)); + assert.deepEqual(s[1], (1 << 15) - 1); + + return [u, s]; + }, + + listMinmax32(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0); + assert.deepEqual(u[1], ~0 >>> 0); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], 1 << 31); + assert.deepEqual(s[1], ((1 << 31) - 1) >>> 0); + + return [u, s]; + }, + + listMinmax64(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0n); + assert.deepEqual(u[1], (2n ** 64n) - 1n); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], -(2n ** 63n)); + assert.deepEqual(s[1], (2n ** 63n) - 1n); + + return [u, s]; + }, + + listMinmaxFloat(f, d) { + assert.deepEqual(f.length, 4); + assert.deepEqual(f[0], -3.4028234663852886e+38); + assert.deepEqual(f[1], 3.4028234663852886e+38); + assert.deepEqual(f[2], Number.NEGATIVE_INFINITY); + assert.deepEqual(f[3], Number.POSITIVE_INFINITY); + + assert.deepEqual(d.length, 4); + assert.deepEqual(d[0], -Number.MAX_VALUE); + assert.deepEqual(d[1], Number.MAX_VALUE); + assert.deepEqual(d[2], Number.NEGATIVE_INFINITY); + assert.deepEqual(d[3], Number.POSITIVE_INFINITY); + + return [f, d]; + } + } + } }); const bytes = wasm.allocatedBytes(); + assert.strictEqual(wasm.test, wasm.listsTest); wasm.testImports(); - wasm.exports.emptyListParam(new Uint8Array([])); - wasm.exports.emptyStringParam(''); - wasm.exports.listParam(new Uint8Array([1, 2, 3, 4]).buffer); - wasm.exports.listParam2("foo"); - wasm.exports.listParam3(["foo", "bar", "baz"]); - wasm.exports.listParam4([["foo", "bar"], ["baz"]]); - assert.deepStrictEqual(Array.from(wasm.exports.emptyListResult()), []); - assert.deepStrictEqual(wasm.exports.emptyStringResult(), ""); - assert.deepStrictEqual(Array.from(wasm.exports.listResult()), [1, 2, 3, 4, 5]); - assert.deepStrictEqual(wasm.exports.listResult2(), "hello!"); - assert.deepStrictEqual(wasm.exports.listResult3(), ["hello,", "world!"]); + wasm.test.emptyListParam(new Uint8Array([])); + wasm.test.emptyStringParam(''); + wasm.test.listParam(new Uint8Array([1, 2, 3, 4]).buffer); + wasm.test.listParam2("foo"); + wasm.test.listParam3(["foo", "bar", "baz"]); + wasm.test.listParam4([["foo", "bar"], ["baz"]]); + assert.deepStrictEqual(Array.from(wasm.test.emptyListResult()), []); + assert.deepStrictEqual(wasm.test.emptyStringResult(), ""); + assert.deepStrictEqual(Array.from(wasm.test.listResult()), [1, 2, 3, 4, 5]); + assert.deepStrictEqual(wasm.test.listResult2(), "hello!"); + assert.deepStrictEqual(wasm.test.listResult3(), ["hello,", "world!"]); const buffer = new ArrayBuffer(8); (new Uint8Array(buffer)).set(new Uint8Array([1, 2, 3, 4]), 2); // Create a view of the four bytes in the middle of the buffer const view = new Uint8Array(buffer, 2, 4); - assert.deepStrictEqual(Array.from(wasm.exports.listRoundtrip(view)), [1, 2, 3, 4]); + assert.deepStrictEqual(Array.from(wasm.test.listRoundtrip(view)), [1, 2, 3, 4]); - assert.deepStrictEqual(wasm.exports.stringRoundtrip("x"), "x"); - assert.deepStrictEqual(wasm.exports.stringRoundtrip(""), ""); - assert.deepStrictEqual(wasm.exports.stringRoundtrip("hello ⚑ world"), "hello ⚑ world"); + assert.deepStrictEqual(wasm.test.stringRoundtrip("x"), "x"); + assert.deepStrictEqual(wasm.test.stringRoundtrip(""), ""); + assert.deepStrictEqual(wasm.test.stringRoundtrip("hello ⚑ world"), "hello ⚑ world"); // Ensure that we properly called `free` everywhere in all the glue that we // needed to. diff --git a/test/runtime/many_arguments.ts b/test/runtime/many-arguments.ts similarity index 95% rename from test/runtime/many_arguments.ts rename to test/runtime/many-arguments.ts index c526450d0..a7f0d4c47 100644 --- a/test/runtime/many_arguments.ts +++ b/test/runtime/many-arguments.ts @@ -1,6 +1,6 @@ // Flags: --instantiation -import { instantiate } from "../output/many_arguments/many_arguments.js"; +import { instantiate } from "../output/many-arguments/many-arguments.js"; import * as helpers from "./helpers.js"; function assertEq(x: any, y: any) { diff --git a/test/runtime/numbers.ts b/test/runtime/numbers.ts index 25b95b1fb..7544811a2 100644 --- a/test/runtime/numbers.ts +++ b/test/runtime/numbers.ts @@ -2,12 +2,7 @@ import * as helpers from "./helpers.js"; import { instantiate } from "../output/numbers/numbers.js"; - -function assertEq(x: any, y: any) { - if (x !== y) - throw new Error(`${x} != ${y}`); -} - +import { strictEqual } from 'node:assert'; function assert(x: boolean) { if (!x) throw new Error("assert failed"); @@ -17,71 +12,75 @@ async function run() { let scalar = 0; const wasm = await instantiate(helpers.loadWasm, { testwasi: helpers, - imports: { - roundtripU8(x) { return x; }, - roundtripS8(x) { return x; }, - roundtripU16(x) { return x; }, - roundtripS16(x) { return x; }, - roundtripU32(x) { return x; }, - roundtripS32(x) { return x; }, - roundtripU64(x) { return x; }, - roundtripS64(x) { return x; }, - roundtripFloat32(x) { return x; }, - roundtripFloat64(x) { return x; }, - roundtripChar(x) { return x; }, - setScalar(x) { scalar = x; }, - getScalar() { return scalar; }, - }, + numbers: { + numbersTest: { + roundtripU8(x) { return x; }, + roundtripS8(x) { return x; }, + roundtripU16(x) { return x; }, + roundtripS16(x) { return x; }, + roundtripU32(x) { return x; }, + roundtripS32(x) { return x; }, + roundtripU64(x) { return x; }, + roundtripS64(x) { return x; }, + roundtripFloat32(x) { return x; }, + roundtripFloat64(x) { return x; }, + roundtripChar(x) { return x; }, + setScalar(x) { scalar = x; }, + getScalar() { return scalar; }, + } + } }); wasm.testImports(); - assertEq(wasm.exports.roundtripU8(1), 1); - assertEq(wasm.exports.roundtripU8((1 << 8) - 1), (1 << 8) - 1); + strictEqual(wasm.numbersTest, wasm.test); + + strictEqual(wasm.test.roundtripU8(1), 1); + strictEqual(wasm.test.roundtripU8((1 << 8) - 1), (1 << 8) - 1); - assertEq(wasm.exports.roundtripS8(1), 1); - assertEq(wasm.exports.roundtripS8((1 << 7) - 1), (1 << 7) - 1); - assertEq(wasm.exports.roundtripS8(-(1 << 7)), -(1 << 7)); + strictEqual(wasm.test.roundtripS8(1), 1); + strictEqual(wasm.test.roundtripS8((1 << 7) - 1), (1 << 7) - 1); + strictEqual(wasm.test.roundtripS8(-(1 << 7)), -(1 << 7)); - assertEq(wasm.exports.roundtripU16(1), 1); - assertEq(wasm.exports.roundtripU16((1 << 16) - 1), (1 << 16) - 1); + strictEqual(wasm.test.roundtripU16(1), 1); + strictEqual(wasm.test.roundtripU16((1 << 16) - 1), (1 << 16) - 1); - assertEq(wasm.exports.roundtripS16(1), 1); - assertEq(wasm.exports.roundtripS16((1 << 15) - 1), (1 << 15) - 1); - assertEq(wasm.exports.roundtripS16(-(1 << 15)), -(1 << 15)); + strictEqual(wasm.test.roundtripS16(1), 1); + strictEqual(wasm.test.roundtripS16((1 << 15) - 1), (1 << 15) - 1); + strictEqual(wasm.test.roundtripS16(-(1 << 15)), -(1 << 15)); - assertEq(wasm.exports.roundtripU32(1), 1); - assertEq(wasm.exports.roundtripU32(~0 >>> 0), ~0 >>> 0); + strictEqual(wasm.test.roundtripU32(1), 1); + strictEqual(wasm.test.roundtripU32(~0 >>> 0), ~0 >>> 0); - assertEq(wasm.exports.roundtripS32(1), 1); - assertEq(wasm.exports.roundtripS32(((1 << 31) - 1) >>> 0), ((1 << 31) - 1) >>> 0); - assertEq(wasm.exports.roundtripS32(1 << 31), 1 << 31); + strictEqual(wasm.test.roundtripS32(1), 1); + strictEqual(wasm.test.roundtripS32(((1 << 31) - 1) >>> 0), ((1 << 31) - 1) >>> 0); + strictEqual(wasm.test.roundtripS32(1 << 31), 1 << 31); - assertEq(wasm.exports.roundtripU64(1n), 1n); - assertEq(wasm.exports.roundtripU64((1n << 64n) - 1n), (1n << 64n) - 1n); + strictEqual(wasm.test.roundtripU64(1n), 1n); + strictEqual(wasm.test.roundtripU64((1n << 64n) - 1n), (1n << 64n) - 1n); - assertEq(wasm.exports.roundtripS64(1n), 1n); - assertEq(wasm.exports.roundtripS64((1n << 63n) - 1n), (1n << 63n) - 1n); - assertEq(wasm.exports.roundtripS64(-(1n << 63n)), -(1n << 63n)); + strictEqual(wasm.test.roundtripS64(1n), 1n); + strictEqual(wasm.test.roundtripS64((1n << 63n) - 1n), (1n << 63n) - 1n); + strictEqual(wasm.test.roundtripS64(-(1n << 63n)), -(1n << 63n)); - assertEq(wasm.exports.roundtripFloat32(1), 1); - assertEq(wasm.exports.roundtripFloat32(Infinity), Infinity); - assertEq(wasm.exports.roundtripFloat32(-Infinity), -Infinity); - assert(Number.isNaN(wasm.exports.roundtripFloat32(NaN))); + strictEqual(wasm.test.roundtripFloat32(1), 1); + strictEqual(wasm.test.roundtripFloat32(Infinity), Infinity); + strictEqual(wasm.test.roundtripFloat32(-Infinity), -Infinity); + assert(Number.isNaN(wasm.test.roundtripFloat32(NaN))); - assertEq(wasm.exports.roundtripFloat64(1), 1); - assertEq(wasm.exports.roundtripFloat64(Infinity), Infinity); - assertEq(wasm.exports.roundtripFloat64(-Infinity), -Infinity); - assert(Number.isNaN(wasm.exports.roundtripFloat64(NaN))); + strictEqual(wasm.test.roundtripFloat64(1), 1); + strictEqual(wasm.test.roundtripFloat64(Infinity), Infinity); + strictEqual(wasm.test.roundtripFloat64(-Infinity), -Infinity); + assert(Number.isNaN(wasm.test.roundtripFloat64(NaN))); - assertEq(wasm.exports.roundtripChar('a'), 'a'); - assertEq(wasm.exports.roundtripChar(' '), ' '); - assertEq(wasm.exports.roundtripChar('🚩'), '🚩'); + strictEqual(wasm.test.roundtripChar('a'), 'a'); + strictEqual(wasm.test.roundtripChar(' '), ' '); + strictEqual(wasm.test.roundtripChar('🚩'), '🚩'); - wasm.exports.setScalar(2); - assertEq(wasm.exports.getScalar(), 2); - wasm.exports.setScalar(4); - assertEq(wasm.exports.getScalar(), 4); + wasm.test.setScalar(2); + strictEqual(wasm.test.getScalar(), 2); + wasm.test.setScalar(4); + strictEqual(wasm.test.getScalar(), 4); } await run() diff --git a/test/runtime/records.ts b/test/runtime/records.ts index 393c3d7ce..638cbec5c 100644 --- a/test/runtime/records.ts +++ b/test/runtime/records.ts @@ -8,44 +8,47 @@ import * as assert from 'node:assert'; async function run() { const wasm = await instantiate(helpers.loadWasm, { testwasi: helpers, - imports: { - multipleResults() { return [4, 5]; }, - swapTuple([a, b]) { return [b, a]; }, - roundtripFlags1(x) { return x; }, - roundtripFlags2(x) { return x; }, - roundtripFlags3(r0, r1, r2, r3) { return [r0, r1, r2, r3]; }, - roundtripRecord1(x) { return x; }, - tuple0([]) { return []; }, - tuple1([x]) { return [x]; }, - }, + records: { + recordsTest: { + multipleResults() { return [4, 5]; }, + swapTuple([a, b]) { return [b, a]; }, + roundtripFlags1(x) { return x; }, + roundtripFlags2(x) { return x; }, + roundtripFlags3(r0, r1, r2, r3) { return [r0, r1, r2, r3]; }, + roundtripRecord1(x) { return x; }, + tuple0([]) { return []; }, + tuple1([x]) { return [x]; }, + } + } }); wasm.testImports(); - assert.deepEqual(wasm.exports.multipleResults(), [100, 200]); - assert.deepStrictEqual(wasm.exports.swapTuple([1, 2]), [2, 1]); - assert.deepEqual(wasm.exports.roundtripFlags1({ a: true }), { a: true, b: false }); - assert.deepEqual(wasm.exports.roundtripFlags1({}), { a: false, b: false }); - assert.deepEqual(wasm.exports.roundtripFlags1({ a: true, b: true }), { a: true, b: true }); - - assert.deepEqual(wasm.exports.roundtripFlags2({ c: true }), { c: true, d: false, e: false }); - assert.deepEqual(wasm.exports.roundtripFlags2({}), { c: false, d: false, e: false }); - assert.deepEqual(wasm.exports.roundtripFlags2({ d: true }), { c: false, d: true, e: false }); - assert.deepEqual(wasm.exports.roundtripFlags2({ c: true, e: true }), { c: true, d: false, e: true }); + assert.deepStrictEqual(wasm.test, wasm.recordsTest); + assert.deepEqual(wasm.test.multipleResults(), [100, 200]); + assert.deepStrictEqual(wasm.test.swapTuple([1, 2]), [2, 1]); + assert.deepEqual(wasm.test.roundtripFlags1({ a: true }), { a: true, b: false }); + assert.deepEqual(wasm.test.roundtripFlags1({}), { a: false, b: false }); + assert.deepEqual(wasm.test.roundtripFlags1({ a: true, b: true }), { a: true, b: true }); + + assert.deepEqual(wasm.test.roundtripFlags2({ c: true }), { c: true, d: false, e: false }); + assert.deepEqual(wasm.test.roundtripFlags2({}), { c: false, d: false, e: false }); + assert.deepEqual(wasm.test.roundtripFlags2({ d: true }), { c: false, d: true, e: false }); + assert.deepEqual(wasm.test.roundtripFlags2({ c: true, e: true }), { c: true, d: false, e: true }); { - const { a, b } = wasm.exports.roundtripRecord1({ a: 8, b: {} }); + const { a, b } = wasm.test.roundtripRecord1({ a: 8, b: {} }); assert.deepEqual(a, 8); assert.deepEqual(b, { a: false, b: false }); } { - const { a, b } = wasm.exports.roundtripRecord1({ a: 0, b: { a: true, b: true } }); + const { a, b } = wasm.test.roundtripRecord1({ a: 0, b: { a: true, b: true } }); assert.deepEqual(a, 0); assert.deepEqual(b, { a: true, b: true }); } - assert.deepStrictEqual(wasm.exports.tuple0([]), []); - assert.deepStrictEqual(wasm.exports.tuple1([1]), [1]); + assert.deepStrictEqual(wasm.test.tuple0([]), []); + assert.deepStrictEqual(wasm.test.tuple1([1]), [1]); } await run() diff --git a/test/runtime/smoke.ts b/test/runtime/smoke.ts index 42580bdb2..9e1c0677b 100644 --- a/test/runtime/smoke.ts +++ b/test/runtime/smoke.ts @@ -1,4 +1,4 @@ -// Flags: --tla-compat --map testwasi=../helpers.js --map imports=../smoke.js --base64-cutoff=2500 +// Flags: --tla-compat --map testwasi=../helpers.js --map test:smoke=../smoke.js --base64-cutoff=2500 function assert(x: boolean, msg: string) { if (!x) throw new Error(msg); @@ -6,9 +6,11 @@ function assert(x: boolean, msg: string) { let hit = false; -export function thunk () { - hit = true; -} +export const smokeImports = { + thunk () { + hit = true; + } +}; async function run() { const wasm = await import('../output/smoke/smoke.js'); diff --git a/test/runtime/strings.ts b/test/runtime/strings.ts index 2c7d8866a..229d374c5 100644 --- a/test/runtime/strings.ts +++ b/test/runtime/strings.ts @@ -9,12 +9,14 @@ import * as assert from 'assert'; async function run() { const wasm = await instantiate(helpers.loadWasm, { testwasi: helpers, - imports: { - takeBasic(s: string) { - assert.strictEqual(s, 'latin utf16'); - }, - returnUnicode() { - return '🚀🚀🚀 𠈄𓀀'; + strings: { + stringsImports: { + takeBasic(s: string) { + assert.strictEqual(s, 'latin utf16'); + }, + returnUnicode() { + return '🚀🚀🚀 𠈄𓀀'; + } } } }); diff --git a/test/runtime/variants.ts b/test/runtime/variants.ts index 9ee9070ba..44fe0b8d8 100644 --- a/test/runtime/variants.ts +++ b/test/runtime/variants.ts @@ -8,47 +8,50 @@ import * as assert from 'assert'; async function run() { const wasm = await instantiate(helpers.loadWasm, { testwasi: helpers, - imports: { - roundtripOption(x) { return x; }, - roundtripResult(x) { - if (x.tag == 'ok') { - return x.val; - } else { - throw Object.assign(new Error(''), { payload: Math.round(x.val) }); + variants: { + variantsTest: { + roundtripOption(x) { return x; }, + roundtripResult(x) { + if (x.tag == 'ok') { + return x.val; + } else { + throw Object.assign(new Error(''), { payload: Math.round(x.val) }); + } + }, + roundtripEnum(x) { return x; }, + invertBool(x) { return !x; }, + variantCasts(x) { return x; }, + variantZeros(x) { return x; }, + variantTypedefs(x, y, z) {}, + variantEnums(a, b, c) { + assert.deepStrictEqual(a, true); + assert.deepStrictEqual(b, { tag: 'ok', val: undefined }); + assert.deepStrictEqual(c, "success"); + return [ + false, + { tag: 'err', val: undefined }, + "a", + ]; } - }, - roundtripEnum(x) { return x; }, - invertBool(x) { return !x; }, - variantCasts(x) { return x; }, - variantZeros(x) { return x; }, - variantTypedefs(x, y, z) {}, - variantEnums(a, b, c) { - assert.deepStrictEqual(a, true); - assert.deepStrictEqual(b, { tag: 'ok', val: undefined }); - assert.deepStrictEqual(c, "success"); - return [ - false, - { tag: 'err', val: undefined }, - "a", - ]; - }, - }, + } + } }); wasm.testImports(); - assert.deepStrictEqual(wasm.exports.roundtripOption(1), 1); - assert.deepStrictEqual(wasm.exports.roundtripOption(null), null); + assert.strictEqual(wasm.test, wasm.variantsTest); + assert.deepStrictEqual(wasm.test.roundtripOption(1), 1); + assert.deepStrictEqual(wasm.test.roundtripOption(null), null); // @ts-ignore - assert.deepStrictEqual(wasm.exports.roundtripOption(undefined), null); + assert.deepStrictEqual(wasm.test.roundtripOption(undefined), null); // @ts-ignore - assert.deepStrictEqual(wasm.exports.roundtripOption(), null); - assert.deepStrictEqual(wasm.exports.roundtripOption(2), 2); - assert.deepStrictEqual(wasm.exports.roundtripResult({ tag: 'ok', val: 2 }), 2); - assert.deepStrictEqual(wasm.exports.roundtripResult({ tag: 'ok', val: 4 }), 4); + assert.deepStrictEqual(wasm.test.roundtripOption(), null); + assert.deepStrictEqual(wasm.test.roundtripOption(2), 2); + assert.deepStrictEqual(wasm.test.roundtripResult({ tag: 'ok', val: 2 }), 2); + assert.deepStrictEqual(wasm.test.roundtripResult({ tag: 'ok', val: 4 }), 4); const f = Math.fround(5.2); try { - wasm.exports.roundtripResult({ tag: 'err', val: f }); + wasm.test.roundtripResult({ tag: 'err', val: f }); assert.fail('Expected an error'); } catch (e: any) { assert.strictEqual(e.constructor.name, 'ComponentError'); @@ -56,14 +59,14 @@ async function run() { assert.strictEqual(e.payload, 5); } - assert.deepStrictEqual(wasm.exports.roundtripEnum("a"), "a"); - assert.deepStrictEqual(wasm.exports.roundtripEnum("b"), "b"); + assert.deepStrictEqual(wasm.test.roundtripEnum("a"), "a"); + assert.deepStrictEqual(wasm.test.roundtripEnum("b"), "b"); - assert.deepStrictEqual(wasm.exports.invertBool(true), false); - assert.deepStrictEqual(wasm.exports.invertBool(false), true); + assert.deepStrictEqual(wasm.test.invertBool(true), false); + assert.deepStrictEqual(wasm.test.invertBool(false), true); { - const [a1, a2, a3, a4, a5, a6] = wasm.exports.variantCasts([ + const [a1, a2, a3, a4, a5, a6] = wasm.test.variantCasts([ { tag: 'a', val: 1 }, { tag: 'a', val: 2 }, { tag: 'a', val: 3 }, @@ -79,7 +82,7 @@ async function run() { assert.deepStrictEqual(a6, { tag: 'a', val: 6 }); } { - const [b1, b2, b3, b4, b5, b6] = wasm.exports.variantCasts([ + const [b1, b2, b3, b4, b5, b6] = wasm.test.variantCasts([ { tag: 'b', val: 1n }, { tag: 'b', val: 2 }, { tag: 'b', val: 3 }, @@ -96,7 +99,7 @@ async function run() { } { - const [a1, a2, a3, a4] = wasm.exports.variantZeros([ + const [a1, a2, a3, a4] = wasm.test.variantZeros([ { tag: 'a', val: 1 }, { tag: 'a', val: 2n }, { tag: 'a', val: 3 }, @@ -108,7 +111,7 @@ async function run() { assert.deepStrictEqual(a4, { tag: 'a', val: 4 }); } - wasm.exports.variantTypedefs(null, false, { tag: 'err', val: undefined }); + wasm.test.variantTypedefs(null, false, { tag: 'err', val: undefined }); } await run() diff --git a/update-preview2.sh b/update-preview2.sh index 340275c11..916c879c2 100755 --- a/update-preview2.sh +++ b/update-preview2.sh @@ -1,5 +1,6 @@ cd lib rm wasi_preview1_component_adapter.command.wasm rm wasi_preview1_component_adapter.reactor.wasm -wget https://github.com/bytecodealliance/preview2-prototyping/releases/download/latest/wasi_preview1_component_adapter.command.wasm -wget https://github.com/bytecodealliance/preview2-prototyping/releases/download/latest/wasi_preview1_component_adapter.reactor.wasm +wget https://github.com/bytecodealliance/wasmtime/releases/download/dev/wasi_snapshot_preview1.command.wasm +wget https://github.com/bytecodealliance/wasmtime/releases/download/dev/wasi_snapshot_preview1.reactor.wasm + diff --git a/update-tests.sh b/update-tests.sh index 7ef97e7be..e321efd48 100755 --- a/update-tests.sh +++ b/update-tests.sh @@ -2,12 +2,15 @@ git submodule init git submodule update cd wit-bindgen git pull origin main -cargo test --workspace --test runtime +cargo test -p wit-bindgen-cli --no-default-features -F c for t in target/runtime-tests/*/c-*/*.component.wasm do name="$(basename $(dirname $t))" name=${name:2} - echo "cp $t ../test/fixtures/${name}.component.wasm" - cp $t ../test/fixtures/${name}.component.wasm + echo "cp $t ../test/fixtures/components/${name}.component.wasm" + cp $t ../test/fixtures/components/${name}.component.wasm done + +# copy flavorful wit case +cp tests/runtime/flavorful/world.wit ../test/fixtures/wit/flavorful.wit diff --git a/wit-bindgen b/wit-bindgen index ca6692e9c..e69cf5db8 160000 --- a/wit-bindgen +++ b/wit-bindgen @@ -1 +1 @@ -Subproject commit ca6692e9cfbb4368a2603e91a7dc035deb23a118 +Subproject commit e69cf5db8754f829637e25491c560ec0d9728852