diff --git a/Cargo.lock b/Cargo.lock index 930f3cf..ff8a1da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,10 +7,6 @@ name = "Inflector" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] [[package]] name = "addr2line" @@ -156,9 +152,9 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" @@ -231,13 +227,12 @@ dependencies = [ [[package]] name = "ast_node" -version = "3.0.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f" +checksum = "2eb025ef00a6da925cf40870b9c8d008526b6004ece399cb0974209720f0b194" dependencies = [ - "proc-macro2", "quote", - "swc_macros_common 1.0.0", + "swc_macros_common", "syn 2.0.87", ] @@ -261,17 +256,6 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -330,16 +314,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen 0.69.5", "cc", "cmake", "dunce", "fs_extra", - "paste", ] [[package]] @@ -465,9 +448,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "better_scoped_tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd297a11c709be8348aec039c8b91de16075d2b2bdaee1bd562c0875993664" +checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" dependencies = [ "scoped-tls", ] @@ -506,9 +489,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.71.1" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags 2.9.0", "cexpr", @@ -687,12 +670,32 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytes-str" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" +dependencies = [ + "bytes", + "serde", +] + [[package]] name = "cache_control" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2a5fb3207c12b5d208ebc145f967fea5cac41a021c37417ccc31ba40f39ee" +[[package]] +name = "calendrical_calculations" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7" +dependencies = [ + "core_maths", + "displaydoc", +] + [[package]] name = "capacity_builder" version = "0.5.0" @@ -779,9 +782,9 @@ checksum = "d5c24f6a463e9973db3df3c2cc276f689f5baf289c87a693dc859e004d3eb45f" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "num-traits", "serde", @@ -880,12 +883,11 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ - "termcolor", - "unicode-width 0.1.13", + "unicode-width 0.2.2", ] [[package]] @@ -944,6 +946,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -952,15 +964,24 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics-types" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 1.3.2", - "core-foundation", + "bitflags 2.9.0", + "core-foundation 0.10.1", "libc", ] +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -1138,6 +1159,12 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -1274,19 +1301,18 @@ dependencies = [ [[package]] name = "deno_ast" -version = "0.49.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24158ccf7def38c00fd253fd1b48c8c6207214078fe499f47168763fa2445bf2" +checksum = "30c2f6f65154faed61e45d6578566f9fab9d2a330c35c87366706883701cce51" dependencies = [ "base64 0.22.1", "capacity_builder", "deno_error", - "deno_media_type", + "deno_media_type 0.3.0", "deno_terminal", "dprint-swc-ext", "percent-encoding", "serde", - "sourcemap", "swc_atoms", "swc_common", "swc_config", @@ -1294,6 +1320,7 @@ dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", "swc_ecma_codegen_macros", + "swc_ecma_lexer", "swc_ecma_loader", "swc_ecma_parser", "swc_ecma_transforms_base", @@ -1305,35 +1332,40 @@ dependencies = [ "swc_ecma_utils", "swc_ecma_visit", "swc_eq_ignore_macros", - "swc_macros_common 1.0.0", + "swc_macros_common", + "swc_sourcemap", "swc_visit", - "swc_visit_macros", "text_lines", "thiserror 2.0.14", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "url", ] [[package]] name = "deno_broadcast_channel" -version = "0.207.0" +version = "0.216.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67a6a80bc318c225eb2f369b23c940cc1a0cf4e5bfbf734a86ac33048e1599b" + +[[package]] +name = "deno_bundle_runtime" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6d1a56c8c5e65b2b2cb42f4409121ada0bb23fdf5de84aa7fdad3fe6227684" +checksum = "7cf499508006f8d999385b025599bfea445ef6bf349acdf43e2e9eb5d49f4fbc" dependencies = [ "async-trait", "deno_core", "deno_error", - "deno_features", - "thiserror 2.0.14", - "tokio", - "uuid", + "deno_web", + "regex", + "serde", ] [[package]] name = "deno_cache" -version = "0.145.0" +version = "0.159.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69582f3db706e3a771364c3556ed4575fa586bf5c54afb481980f25bc230df91" +checksum = "a18c94da036f1d6c8afe8696d4ffdbeff0c59b484edf4e8594517ca2e13797a0" dependencies = [ "async-stream", "async-trait", @@ -1360,9 +1392,9 @@ dependencies = [ [[package]] name = "deno_cache_dir" -version = "0.24.0" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "392538d71e0ce9db316302116095b8acef4d734a8e2360d16308ca7dc32dcdc3" +checksum = "7862aa4b7aad895ee979a3078bd25da192a1810b30cb5a7e764fed38d065eba9" dependencies = [ "async-trait", "base32", @@ -1372,7 +1404,7 @@ dependencies = [ "chrono", "data-url", "deno_error", - "deno_media_type", + "deno_media_type 0.3.0", "deno_path_util", "http 1.1.0", "indexmap 2.9.0", @@ -1387,30 +1419,17 @@ dependencies = [ "url", ] -[[package]] -name = "deno_canvas" -version = "0.82.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf10aafd8b86b6bc1019b68249f70be4c70d49a6f957bc5658afea78dbaa6dd" -dependencies = [ - "bytemuck", - "deno_core", - "deno_error", - "image", - "lcms2", - "num-traits", - "thiserror 2.0.14", -] - [[package]] name = "deno_config" -version = "0.63.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf67de1d3be71b9449f297dfe685c7c2b6911949fb25f1c3a53fb3190549cf4" +checksum = "f7a46657bd5ab6bd9df0602fd50331c49c66706fe2a6386fe6a1c9b81f3031b3" dependencies = [ "boxed_error", "capacity_builder", + "chrono", "deno_error", + "deno_maybe_sync", "deno_package_json", "deno_path_util", "deno_semver", @@ -1429,18 +1448,15 @@ dependencies = [ [[package]] name = "deno_console" -version = "0.213.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c53d2fbfe68ff0d39dcbffb869bfa30019da2db5d0efbfb4b3fcc04bd1aed0e" -dependencies = [ - "deno_core", -] +checksum = "ed7dede3d073a7d19e5e127756d80a1a8b570d8345e51192968da142ed290319" [[package]] name = "deno_core" -version = "0.355.0" +version = "0.376.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775d2fde80a2ec3116d179703b38346a931bb9626f4a826148d5fe8631cab29f" +checksum = "829ee94e76838e99951cc7b3ccd31f68a220687f52229c0d6e135ff29b22f6d0" dependencies = [ "anyhow", "az", @@ -1483,9 +1499,9 @@ checksum = "fe4dccb6147bb3f3ba0c7a48e993bfeb999d2c2e47a81badee80e2b370c8d695" [[package]] name = "deno_cron" -version = "0.93.0" +version = "0.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d52e26778837fcd8067416270f34e4e2e1160248f9d75d8d9c9dd58bd90991" +checksum = "13bab29168443140e5914af8a2358dfb8158898b2a73ef33839cbcb31074220c" dependencies = [ "async-trait", "chrono", @@ -1499,9 +1515,9 @@ dependencies = [ [[package]] name = "deno_crypto" -version = "0.227.0" +version = "0.240.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f8afd2869b8cefa809e912ba73242485ac69009b6195581eea8a75bc1b89c7" +checksum = "b235765571dca50710542ec2305b936c2c435f5efe632307cf6b2735e0449268" dependencies = [ "aes", "aes-gcm", @@ -1539,18 +1555,18 @@ dependencies = [ [[package]] name = "deno_crypto_provider" -version = "0.7.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c915335e9f5e628dea0029ef443f99ca8a5421803a361fdda4ad5b5370c75" +checksum = "a4e486cef731046954a2eaa9ecd71669101ac2ceac61f2dbdad1c3a6eac5ac5c" dependencies = [ - "aws-lc-sys 0.26.0", + "aws-lc-sys 0.29.0", ] [[package]] name = "deno_error" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde60bd153886964234c5012d3d9caf788287f28d81fb24a884436904101ef10" +checksum = "bfafd2219b29886a71aecbb3449e462deed1b2c474dc5b12f855f0e58c478931" dependencies = [ "deno_error_macro", "libc", @@ -1562,9 +1578,9 @@ dependencies = [ [[package]] name = "deno_error_macro" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409f265785bd946d3006756955aaf40b0e4deb25752eae6a990afe54a31cfd83" +checksum = "1c28ede88783f14cd8aae46ca89f230c226b40e4a81ab06fa52ed72af84beb2f" dependencies = [ "proc-macro2", "quote", @@ -1573,9 +1589,9 @@ dependencies = [ [[package]] name = "deno_features" -version = "0.10.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73252505f94efc5be0e5c8255b8d2b7e4c1b6361a36c033486ae83d2cddbd37" +checksum = "45ef52b29c98bd0461f705d5ef6213346c028110b364f5d84e6151710832075a" dependencies = [ "deno_core", "serde", @@ -1584,9 +1600,9 @@ dependencies = [ [[package]] name = "deno_fetch" -version = "0.237.0" +version = "0.250.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f05820261bcc82d377f79878625d61ac70b58c886345e5b1d68f6b012ae4765" +checksum = "d2cedb5a547dba738cff57f879fde761356bdd23b80668de93cd95ee0f8b92f3" dependencies = [ "base64 0.22.1", "bytes", @@ -1624,9 +1640,9 @@ dependencies = [ [[package]] name = "deno_ffi" -version = "0.200.0" +version = "0.213.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f76fbe2a28bbd9af9767b3e262a5da732759f4095347ec8ab9cf183e7888a3" +checksum = "7ba7fff4f630257bfc405a3ccb88a59e48b09bac5dfdc72155666b28ec001760" dependencies = [ "cranelift", "cranelift-native", @@ -1650,9 +1666,9 @@ dependencies = [ [[package]] name = "deno_fs" -version = "0.123.0" +version = "0.136.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b670b3bbceb5aee7e840a4e960e0e0def8f3cf5554f191035b209f38f555638" +checksum = "02bfffd3f56099d7bc5d1622d367e2c91ce860b0bb7939c2b790c46af184aed7" dependencies = [ "async-trait", "base32", @@ -1660,6 +1676,7 @@ dependencies = [ "deno_core", "deno_error", "deno_io", + "deno_maybe_sync", "deno_path_util", "deno_permissions", "filetime", @@ -1676,9 +1693,9 @@ dependencies = [ [[package]] name = "deno_http" -version = "0.211.0" +version = "0.224.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769c9b785e7f87b5e6a5f2aab7b2b74d482670f51b2a7f1a104089f7afe28ea2" +checksum = "5a23fe4d69546e79820c112296e37e6b20109838de9981682770fbd444edec83" dependencies = [ "async-compression", "async-trait", @@ -1689,6 +1706,7 @@ dependencies = [ "cache_control", "deno_core", "deno_error", + "deno_fetch", "deno_net", "deno_telemetry", "deno_websocket", @@ -1716,11 +1734,26 @@ dependencies = [ "tokio-vsock", ] +[[package]] +name = "deno_image" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3aca5b0d43db3b05e998604ce5517989d7f36af2fd693884feddf70aa85b5f6" +dependencies = [ + "bytemuck", + "deno_core", + "deno_error", + "image", + "lcms2", + "num-traits", + "thiserror 2.0.14", +] + [[package]] name = "deno_io" -version = "0.123.0" +version = "0.136.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5ed7012795555df1a65441c416e1a699d596c8ccf26c346fe6db6d35cd60336" +checksum = "c75b313e7f2311c23dff33d35e8561b9786440fbaa377fe3f873fb0dc461b82f" dependencies = [ "async-trait", "deno_core", @@ -1745,9 +1778,9 @@ dependencies = [ [[package]] name = "deno_kv" -version = "0.121.0" +version = "0.134.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edd0dcf23f34424364f3634dc2327af996285436245c40524068ba6129bba2c" +checksum = "cfc81b0449840666f9d6e8f142747eb3edc1b436cf712f49d307fb868a47dec0" dependencies = [ "anyhow", "async-trait", @@ -1780,9 +1813,9 @@ dependencies = [ [[package]] name = "deno_lockfile" -version = "0.31.2" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5afb20b44a44842178f41fa3b713c2b1516062590b5cca2ad792f2d6786bc74f" +checksum = "3d71c0df1464034be21a9472e7ec8f9a21958418d203fa2c40507fb5cafe799d" dependencies = [ "async-trait", "deno_semver", @@ -1791,6 +1824,15 @@ dependencies = [ "thiserror 2.0.14", ] +[[package]] +name = "deno_maybe_sync" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6716d359b3fe9d3fc6ab08f39e463ff9a63033041fe6aaeecf1930b284474b9b" +dependencies = [ + "dashmap", +] + [[package]] name = "deno_media_type" version = "0.2.9" @@ -1802,11 +1844,22 @@ dependencies = [ "url", ] +[[package]] +name = "deno_media_type" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac0109d26ff08a089642a79b45c65f91a849404c1ef3ec78c837a412956d8808" +dependencies = [ + "data-url", + "serde", + "url", +] + [[package]] name = "deno_napi" -version = "0.144.0" +version = "0.157.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5102d9e32e88c1cbb3034594679a3e3902b295077c0c2a24069dc98c2f6c8aae" +checksum = "4a1eace0f5a480e510d5c501660f41bcf8a35b192969868c3a411fa4e95d9815" dependencies = [ "deno_core", "deno_error", @@ -1835,9 +1888,9 @@ dependencies = [ [[package]] name = "deno_net" -version = "0.205.0" +version = "0.218.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5fcbc6f531c0ee8e7be34bc877aef1ed20c94b7d2215929ea176280259fc7a" +checksum = "6a7c7f51d5e5f695c56596d6de2afeb2ddb26d432506dba2edfd44f61c869c79" dependencies = [ "deno_core", "deno_error", @@ -1848,6 +1901,7 @@ dependencies = [ "deno_tunnel", "hickory-proto", "hickory-resolver", + "libc", "log", "pin-project", "quinn", @@ -1864,9 +1918,9 @@ dependencies = [ [[package]] name = "deno_node" -version = "0.151.0" +version = "0.164.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fccfb2ea5216cb8112861851771886b26f6341232d44e9676bddcb937b7754" +checksum = "1ae702dad0dec327be07e23a0fb243f1be7a59a43a9964fb48ca867fa8618d8d" dependencies = [ "aead-gcm-stream", "aes", @@ -1892,6 +1946,7 @@ dependencies = [ "deno_permissions", "deno_process", "deno_subprocess_windows", + "deno_tls", "deno_whoami", "der", "digest", @@ -1914,8 +1969,10 @@ dependencies = [ "k256", "libc", "libz-sys", + "log", "md-5", "md4", + "nix 0.27.1", "node_resolver", "num-bigint", "num-bigint-dig", @@ -1931,6 +1988,7 @@ dependencies = [ "ripemd", "rsa", "rusqlite", + "rustls-tokio-stream", "scrypt", "sec1", "serde", @@ -1939,6 +1997,7 @@ dependencies = [ "sha3", "signature", "sm3", + "socket2 0.5.7", "spki", "sys_traits", "thiserror 2.0.14", @@ -1951,17 +2010,18 @@ dependencies = [ "windows-sys 0.59.0", "x25519-dalek", "x509-parser", - "yoke", + "yoke 0.7.4", ] [[package]] name = "deno_npm" -version = "0.36.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688f93cb13db227a013bfd5c5973235458615115934fc37ce2e72620c9934526" +checksum = "7daa908c280b25d68ea59765144df488714716cc14a2dcafaa1ec3906d4abbbe" dependencies = [ "async-trait", "capacity_builder", + "chrono", "deno_error", "deno_lockfile", "deno_semver", @@ -1977,26 +2037,26 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.231.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca530772bbcbc9ad389ad7bcd86623b2ec555f68a2d062d23cc008915cbe781" +checksum = "c83a1be112eed3cb12e02c541f0da2a7357655aa519f3f180c405c0409cfa5ea" dependencies = [ "indexmap 2.9.0", - "proc-macro-rules", "proc-macro2", "quote", "stringcase", - "strum 0.27.1", - "strum_macros 0.27.1", + "strum", + "strum_macros", "syn 2.0.87", + "syn-match", "thiserror 2.0.14", ] [[package]] name = "deno_os" -version = "0.30.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2b0b7c124499ae48516464a3636b900cdecd765c7e6e411f813038b3e1c575" +checksum = "1d7ca2f93af2e4296b1bbb09ed65093925ae6aacf02e64c645f9f4be31763b15" dependencies = [ "deno_core", "deno_error", @@ -2015,12 +2075,13 @@ dependencies = [ [[package]] name = "deno_package_json" -version = "0.15.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36155ebe3074c582c533895056071dc05036548e40420bd977cc41b822a3dc49" +checksum = "068ca274a783b395305290a1e606469c80123f9a5a542f51e9b817e03e3b2796" dependencies = [ "boxed_error", "deno_error", + "deno_maybe_sync", "deno_path_util", "deno_semver", "indexmap 2.9.0", @@ -2033,9 +2094,9 @@ dependencies = [ [[package]] name = "deno_path_util" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe02936964b2910719bd488841f6e884349360113c7abf6f4c6b28ca9cd7a19" +checksum = "78c7e98943f0d068928906db0c7bde89de684fa32c6a8018caacc4cee2cdd72b" dependencies = [ "deno_error", "percent-encoding", @@ -2046,11 +2107,12 @@ dependencies = [ [[package]] name = "deno_permissions" -version = "0.72.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e08943b9430d1e78b7ddae254666aadfe5d8b3ee5cb253f1dff872ef5b22f10" +checksum = "ea134204d5c84e0a186de208c410e4c38c839cea308e579037e747405affb947" dependencies = [ "capacity_builder", + "chrono", "deno_error", "deno_path_util", "deno_terminal", @@ -2075,9 +2137,9 @@ dependencies = [ [[package]] name = "deno_process" -version = "0.28.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e378d7a21caef5ac9e6b7e23247dd95c71c702aaae9c73fc8aecced2f330fbf" +checksum = "e5a85f57bec6a19f2e3634d9905543d5821fe28691a12bec0183389999e7f11e" dependencies = [ "deno_core", "deno_error", @@ -2106,9 +2168,9 @@ dependencies = [ [[package]] name = "deno_resolver" -version = "0.44.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaf3b5a2f5d25e1cf45e8e4435465d413cc5ebc1f5ac286aa977165d26a7fc1" +checksum = "cdc21fb4cbd83f363f2ce32c51df542e110ecac9d22f4c36dfac2bdb7c6d5dd8" dependencies = [ "anyhow", "async-once-cell", @@ -2116,12 +2178,14 @@ dependencies = [ "base32", "boxed_error", "capacity_builder", + "chrono", "dashmap", "deno_cache_dir", "deno_config", "deno_error", "deno_lockfile", - "deno_media_type", + "deno_maybe_sync", + "deno_media_type 0.3.0", "deno_npm", "deno_package_json", "deno_path_util", @@ -2148,15 +2212,14 @@ dependencies = [ [[package]] name = "deno_runtime" -version = "0.221.0" +version = "0.234.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1615b74c4632c7d0d91b58cdbce233e3c662aa1460d02b0a68acc8e2d9af46bf" +checksum = "bfa19bcc64b53ce47ce91aa870f83c6737fabb4b9194b720f09a71e24a8ee15c" dependencies = [ + "async-trait", "color-print", - "deno_broadcast_channel", + "deno_bundle_runtime", "deno_cache", - "deno_canvas", - "deno_console", "deno_core", "deno_cron", "deno_crypto", @@ -2166,6 +2229,7 @@ dependencies = [ "deno_ffi", "deno_fs", "deno_http", + "deno_image", "deno_io", "deno_kv", "deno_napi", @@ -2180,7 +2244,6 @@ dependencies = [ "deno_telemetry", "deno_terminal", "deno_tls", - "deno_url", "deno_web", "deno_webgpu", "deno_webidl", @@ -2192,6 +2255,7 @@ dependencies = [ "http-body-util", "hyper 1.6.0", "hyper-util", + "indexmap 2.9.0", "libc", "log", "nix 0.27.1", @@ -2199,6 +2263,7 @@ dependencies = [ "notify", "ntapi", "once_cell", + "regex", "rustyline", "same-file", "serde", @@ -2214,9 +2279,9 @@ dependencies = [ [[package]] name = "deno_semver" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2625b7107cc3f61a462886d5fa77c23e063c1fd15b90e3d5ee2646e9f6178d55" +checksum = "92d46d2fd6959170a6e9f6607a6f79683868fa82ceac56ca520ab014e4fa5b21" dependencies = [ "capacity_builder", "deno_error", @@ -2231,9 +2296,9 @@ dependencies = [ [[package]] name = "deno_signals" -version = "0.4.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edcc759556d2d3170bcb5f430c2a3b2ed873211af3c360cc77523ea79b09ffa" +checksum = "1d963f791b2bd211be5c9c18b2a3a83ff672f5774a7425e605127c8450956961" dependencies = [ "deno_error", "libc", @@ -2245,9 +2310,9 @@ dependencies = [ [[package]] name = "deno_subprocess_windows" -version = "0.8.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63a79fb81598d64bd3adc801fae8f9f6930f4a790f50ff0196e0fe20c92fab46" +checksum = "921d3801fcfb9e6f8e287f8cacd56f9dc801e27dd57ba67132f6c2960e114566" dependencies = [ "fastrand", "futures-channel", @@ -2257,9 +2322,9 @@ dependencies = [ [[package]] name = "deno_telemetry" -version = "0.35.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80894b874df0ade53b85fd094ff948d645cfe05b5319761f922391b260b111de" +checksum = "f825c3cd1de8c814cff65bbe8f1019735edd6781b5a0dcbba4f27bb403e89de8" dependencies = [ "async-trait", "deno_core", @@ -2288,9 +2353,9 @@ dependencies = [ [[package]] name = "deno_terminal" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f71c27009e0141dedd315f1dfa3ebb0a6ca4acce7c080fac576ea415a465f6" +checksum = "f3ba8041ae7319b3ca6a64c399df4112badcbbe0868b4517637647614bede4be" dependencies = [ "once_cell", "termcolor", @@ -2298,9 +2363,9 @@ dependencies = [ [[package]] name = "deno_tls" -version = "0.200.0" +version = "0.213.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30245c362adc1e6b74b28c0a4687950c2f0f8729fc62e05f11db29f31d2dd37b" +checksum = "a96a4a3cff3c0bbd77342fd5093732a64840fdd7e76868723fea80751bbcc0e1" dependencies = [ "deno_core", "deno_error", @@ -2312,7 +2377,7 @@ dependencies = [ "serde", "thiserror 2.0.14", "tokio", - "webpki-roots", + "webpki-roots 0.26.3", ] [[package]] @@ -2343,26 +2408,22 @@ dependencies = [ [[package]] name = "deno_url" -version = "0.213.0" +version = "0.222.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ebe63615f18221afbdcf0dd97f128af21529c6a4d012a7b9b4a0223c91359b2" -dependencies = [ - "deno_core", - "deno_error", - "urlpattern", -] +checksum = "54c190c36940b8ba25043e2258775df3cd4af06f28c8a9efd28b50799efe2977" [[package]] name = "deno_web" -version = "0.244.0" +version = "0.257.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a863aae15f3dbccb11b3776e9dea399f510a9b1d4dac22a4dcfff96bcff9d7" +checksum = "cc732e3f665b5117f92b645c2a959bac3dd1dfa5d3f74f4de11dbe90c4c23b20" dependencies = [ "async-trait", "base64-simd", "bytes", "deno_core", "deno_error", + "deno_features", "deno_permissions", "encoding_rs", "flate2", @@ -2370,14 +2431,15 @@ dependencies = [ "serde", "thiserror 2.0.14", "tokio", + "urlpattern", "uuid", ] [[package]] name = "deno_webgpu" -version = "0.180.0" +version = "0.193.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "becd03f8f02cda17d3a94e6acdf4e64224564ccc77b03159d9e0b83c8828c615" +checksum = "bb8b2c887355112abfa14e1596240836f3ece16897d63010a9e9bb3cc3959332" dependencies = [ "deno_core", "deno_error", @@ -2394,22 +2456,23 @@ dependencies = [ [[package]] name = "deno_webidl" -version = "0.213.0" +version = "0.226.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68682e535768112593274795b70f4dee5d31d6d973f4be14d660c0a8954e0abb" +checksum = "a16ef8ede69eb140bd5577a89f1c2d7e068dd3a7de29ea7b46f9e0e087cbec1c" dependencies = [ "deno_core", ] [[package]] name = "deno_websocket" -version = "0.218.0" +version = "0.231.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62497c70af5f4e4ea4027f88a80cb295294bc9a032988bb092085aef07b3208" +checksum = "8ed1fd12275bd76c460175362575269b90e6ee17df5ac3bd353b6899ba77d4e7" dependencies = [ "bytes", "deno_core", "deno_error", + "deno_fetch", "deno_net", "deno_permissions", "deno_tls", @@ -2428,9 +2491,9 @@ dependencies = [ [[package]] name = "deno_webstorage" -version = "0.208.0" +version = "0.221.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517bc591568d2382b2288bfb581cd6630070ec800ba98d55ca299fff193861a4" +checksum = "d89134c225a3b3ee48e0300813b56f747eeb0edef044d93c3b07124fbc14c30e" dependencies = [ "deno_core", "deno_error", @@ -2450,9 +2513,9 @@ dependencies = [ [[package]] name = "denokv_proto" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cfd5bdec33d2e5c4188ea2a7b348eca13958afa4c76a11504d9aa0efca589" +checksum = "3c9f1d5365706efe37950bfa83e09fbf6efe5f42306570f05dab72cd2bf3c420" dependencies = [ "async-trait", "chrono", @@ -2466,9 +2529,9 @@ dependencies = [ [[package]] name = "denokv_remote" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91a6b359d69cb44e4390f2bcd007f72c252c6b9f6f88c7eab8f9bb55806fbc1a" +checksum = "47161e738042861f890ac51c0bf42b73c9372f483ffa6ec7e4d26d8a68e136d0" dependencies = [ "async-stream", "async-trait", @@ -2492,9 +2555,9 @@ dependencies = [ [[package]] name = "denokv_sqlite" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f119506add29e620d1a6d37903d727a6abab0feb0f2e898554b0a10d1113ab32" +checksum = "04812f283a0bcc7e8f08f2d6be989b3710af83324b1b9b5704881b779cfbfb02" dependencies = [ "async-stream", "async-trait", @@ -2517,9 +2580,9 @@ dependencies = [ [[package]] name = "denort_helper" -version = "0.11.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8f00b1155cd5a060c3fe59006bf5251f763a31e51ce55f9924c0160a58d4" +checksum = "8a0526b641bde8e09fb9bcd0d3fb79e5d42181ceafbe368ebe12ef6806959b5f" dependencies = [ "deno_error", "deno_path_util", @@ -2611,6 +2674,38 @@ dependencies = [ "subtle", ] +[[package]] +name = "diplomat" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adb46b05e2f53dcf6a7dfc242e4ce9eb60c369b6b6eb10826a01e93167f59c6" +dependencies = [ + "diplomat_core", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "diplomat-runtime" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0569bd3caaf13829da7ee4e83dbf9197a0e1ecd72772da6d08f0b4c9285c8d29" + +[[package]] +name = "diplomat_core" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51731530ed7f2d4495019abc7df3744f53338e69e2863a6a64ae91821c763df1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "smallvec", + "strck", + "syn 2.0.87", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -2668,15 +2763,16 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dprint-swc-ext" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a09827d6db1a3af25e105553d674ee9019be58fa3d6745c2a2803f8ce8e3eb8" +checksum = "33175ddb7a6d418589cab2966bd14a710b3b1139459d3d5ca9edf783c4833f4c" dependencies = [ "num-bigint", "rustc-hash 2.1.1", "swc_atoms", "swc_common", "swc_ecma_ast", + "swc_ecma_lexer", "swc_ecma_parser", "text_lines", ] @@ -2906,9 +3002,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastwebsockets" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26da0c7b5cef45c521a6f9cdfffdfeb6c9f5804fbac332deb5ae254634c7a6be" +checksum = "9dac026e15fb7e44d768880b868a0fd5bd30ffdee272e88b3060f657a5a72947" dependencies = [ "base64 0.21.7", "bytes", @@ -3009,6 +3105,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.5.0" @@ -3038,27 +3140,26 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "fqdn" -version = "0.3.11" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08b1eaa7dfddeab6036292995620bf0435712e619db6d7690605897e76975eb0" +checksum = "fe2713644726a30a74150dc8d10e910a8e14e431e1a4f98988309da8c80ddf7a" [[package]] name = "from_variant" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" +checksum = "e5ff35a391aef949120a0340d690269b3d9f63460a6106e99bd07b961f345ea9" dependencies = [ - "proc-macro2", - "swc_macros_common 1.0.0", + "swc_macros_common", "syn 2.0.87", ] @@ -3203,7 +3304,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows", + "windows 0.58.0", ] [[package]] @@ -3322,46 +3423,29 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.9.0", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.9.0", -] - [[package]] name = "gpu-allocator" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" dependencies = [ + "ash", + "hashbrown 0.16.1", "log", "presser", - "thiserror 1.0.66", - "windows", + "thiserror 2.0.14", + "windows 0.62.2", ] [[package]] name = "gpu-descriptor" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] @@ -3433,12 +3517,15 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "num-traits", + "serde", + "zerocopy 0.8.27", ] [[package]] @@ -3482,11 +3569,24 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] -name = "hashlink" +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" @@ -3550,11 +3650,10 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hickory-proto" -version = "0.25.0-alpha.5" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d00147af6310f4392a31680db52a3ed45a2e0f68eb18e8c3fe5537ecc96d9e2" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ - "async-recursion", "async-trait", "cfg-if", "data-encoding", @@ -3566,6 +3665,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.9.0", + "ring", "serde", "thiserror 2.0.14", "tinyvec", @@ -3576,9 +3676,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.25.0-alpha.5" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5762f69ebdbd4ddb2e975cd24690bf21fe6b2604039189c26acddbc427f12887" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", @@ -3647,15 +3747,15 @@ dependencies = [ [[package]] name = "hstr" -version = "1.0.0" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71399f53a92ef72ee336a4b30201c6e944827e14e0af23204c291aad9c24cc85" +checksum = "0c43c0a9e8fbdb3bb9dc8eee85e1e2ac81605418b4c83b6b7413cbf14d56ca5c" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", "once_cell", - "phf", "rustc-hash 2.1.1", + "serde", "triomphe", ] @@ -3787,7 +3887,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 0.26.3", ] [[package]] @@ -3805,23 +3905,50 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.1.0", "http-body 1.0.1", "hyper 1.6.0", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.6.0", "tokio", "tower-service", "tracing", ] +[[package]] +name = "icu_calendar" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e" +dependencies = [ + "calendrical_calculations", + "displaydoc", + "icu_calendar_data", + "icu_locale", + "icu_locale_core", + "icu_provider 2.1.1", + "tinystr 0.8.2", + "zerovec 0.11.5", +] + +[[package]] +name = "icu_calendar_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d" + [[package]] name = "icu_collections" version = "1.5.0" @@ -3829,11 +3956,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", - "yoke", + "yoke 0.7.4", + "zerofrom", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke 0.8.1", "zerofrom", - "zerovec", + "zerovec 0.11.5", +] + +[[package]] +name = "icu_locale" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60" +dependencies = [ + "icu_collections 2.1.1", + "icu_locale_core", + "icu_locale_data", + "icu_provider 2.1.1", + "potential_utf", + "tinystr 0.8.2", + "zerovec 0.11.5", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap 0.8.1", + "serde", + "tinystr 0.8.2", + "writeable 0.6.2", + "zerovec 0.11.5", ] +[[package]] +name = "icu_locale_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03e2fcaefecdf05619f3d6f91740e79ab969b4dd54f77cbf546b1d0d28e3147" + [[package]] name = "icu_locid" version = "1.5.0" @@ -3841,10 +4016,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "litemap 0.7.3", + "tinystr 0.7.6", + "writeable 0.5.5", + "zerovec 0.10.4", ] [[package]] @@ -3856,9 +4031,9 @@ dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", ] [[package]] @@ -3874,15 +4049,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", - "icu_collections", + "icu_collections 1.5.0", "icu_normalizer_data", - "icu_properties", - "icu_provider", + "icu_properties 1.5.1", + "icu_provider 1.5.0", "smallvec", "utf16_iter", "utf8_iter", "write16", - "zerovec", + "zerovec 0.10.4", ] [[package]] @@ -3898,12 +4073,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", - "icu_collections", + "icu_collections 1.5.0", "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", + "icu_properties_data 1.5.0", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections 2.1.1", + "icu_locale_core", + "icu_properties_data 2.1.2", + "icu_provider 2.1.1", + "zerotrie", + "zerovec 0.11.5", ] [[package]] @@ -3912,6 +4101,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + [[package]] name = "icu_provider" version = "1.5.0" @@ -3922,11 +4117,28 @@ dependencies = [ "icu_locid", "icu_provider_macros", "stable_deref_trait", - "tinystr", - "writeable", - "yoke", + "tinystr 0.7.6", + "writeable 0.5.5", + "yoke 0.7.4", + "zerofrom", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable 0.6.2", + "yoke 0.8.1", "zerofrom", - "zerovec", + "zerotrie", + "zerovec 0.11.5", ] [[package]] @@ -3942,9 +4154,9 @@ dependencies = [ [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -3958,7 +4170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", - "icu_properties", + "icu_properties 1.5.1", ] [[package]] @@ -3999,9 +4211,9 @@ dependencies = [ [[package]] name = "import_map" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce93e07f4819f0db4d2948fffce3ef4760a9940c4ff4f9369dfaf5e357a0d415" +checksum = "f83a4958a41489355816028239fee373797435384d162f4908e7980c83c3bb1b" dependencies = [ "boxed_error", "deno_error", @@ -4103,6 +4315,16 @@ dependencies = [ "serde", ] +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-macro" version = "0.3.5" @@ -4159,6 +4381,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "ixdtf" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992" + [[package]] name = "jni-sys" version = "0.3.0" @@ -4186,9 +4414,9 @@ dependencies = [ [[package]] name = "jsonc-parser" -version = "0.26.3" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6d80e6d70e7911a29f3cf3f44f452df85d06f73572b494ca99a2cad3fcf8f4" +checksum = "01958dcb05b69d9612853b47df8f7881810e4f61b5cedd8894be04291f28ccb9" dependencies = [ "serde_json", ] @@ -4332,9 +4560,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libffi" -version = "4.0.0" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9434b6fc77375fb624698d5f8c49d7e80b10d59eb1219afda27d1f824d4074" +checksum = "b0feebbe0ccd382a2790f78d380540500d7b78ed7a3498b68fcfbc1593749a94" dependencies = [ "libc", "libffi-sys", @@ -4342,9 +4570,9 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "3.2.0" +version = "3.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead36a2496acfc8edd6cc32352110e9478ac5b9b5f5b9856ebd3d28019addb84" +checksum = "90c6c6e17136d4bc439d43a2f3c6ccf0731cccc016d897473a29791d3c2160c3" dependencies = [ "cc", ] @@ -4377,11 +4605,11 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.32.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" dependencies = [ - "bindgen 0.71.1", + "bindgen 0.72.1", "cc", "pkg-config", "vcpkg", @@ -4410,6 +4638,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "litrs" version = "0.4.1" @@ -4428,9 +4662,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "loom" @@ -4451,6 +4685,22 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "macro_rules_attribute" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -4535,9 +4785,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.31.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" dependencies = [ "bitflags 2.9.0", "block", @@ -4630,32 +4880,36 @@ checksum = "b52c1b33ff98142aecea13138bd399b68aa7ab5d9546c300988c345004001eea" [[package]] name = "naga" -version = "24.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" +checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" dependencies = [ "arrayvec", "bit-set", "bitflags 2.9.0", + "cfg-if", "cfg_aliases", "codespan-reporting", + "half", + "hashbrown 0.16.1", "hexf-parse", "indexmap 2.9.0", + "libm", "log", + "num-traits", + "once_cell", "rustc-hash 1.1.0", "serde", "spirv", - "strum 0.26.3", - "termcolor", "thiserror 2.0.14", - "unicode-xid", + "unicode-ident", ] [[package]] name = "napi_sym" -version = "0.143.0" +version = "0.156.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cda32f648f67ca18ff3612a03e89adcf25ecdbc7db0e765831f64fbc8d164d9" +checksum = "a912b4949264d90faa47e3bd7ebf91743530daf61c811dafa11497bf8fc001fe" dependencies = [ "quote", "serde", @@ -4665,9 +4919,9 @@ dependencies = [ [[package]] name = "ndk-sys" -version = "0.5.0+25.2.9519653" +version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ "jni-sys", ] @@ -4706,6 +4960,7 @@ dependencies = [ "bitflags 2.9.0", "cfg-if", "libc", + "memoffset", ] [[package]] @@ -4723,9 +4978,9 @@ dependencies = [ [[package]] name = "node_resolver" -version = "0.51.0" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ea72d52f389d704af50e11c7988f06aa41625595569450354b84e2a93ef0a" +checksum = "6e801b86d09845a0e35dc4f148ec435746bab0632a7f48384883244094733548" dependencies = [ "anyhow", "async-trait", @@ -4733,12 +4988,14 @@ dependencies = [ "capacity_builder", "dashmap", "deno_error", - "deno_media_type", + "deno_maybe_sync", + "deno_media_type 0.3.0", "deno_package_json", "deno_path_util", "deno_semver", "futures", "lazy-regex", + "log", "once_cell", "path-clean", "pretty_assertions", @@ -4822,11 +5079,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -4916,6 +5172,10 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "oorandom" @@ -5116,23 +5376,13 @@ dependencies = [ [[package]] name = "par-core" -version = "1.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757892557993c69e82f9de0f9051e87144278aa342f03bf53617bbf044554484" +checksum = "e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f" dependencies = [ "once_cell", ] -[[package]] -name = "par-iter" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a5b20f31e9ba82bfcbbb54a67aa40be6cebec9f668ba5753be138f9523c531a" -dependencies = [ - "either", - "par-core", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -5206,9 +5456,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" @@ -5387,6 +5637,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "serde_core", + "writeable 0.6.2", + "zerovec 0.11.5", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -5434,29 +5695,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-rules" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" -dependencies = [ - "proc-macro-rules-macros", - "proc-macro2", - "syn 2.0.87", -] - -[[package]] -name = "proc-macro-rules-macros" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "207fffb0fe655d1d47f6af98cc2793405e85929bdbc420d685554ff07be27ac7" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "proc-macro2" version = "1.0.92" @@ -5504,26 +5742,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ptr_meta" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "pulldown-cmark" version = "0.9.6" @@ -5641,7 +5859,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.23", + "zerocopy 0.8.27", ] [[package]] @@ -5696,9 +5914,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -5706,9 +5924,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -5812,9 +6030,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", @@ -5827,16 +6045,12 @@ dependencies = [ "hyper 1.6.0", "hyper-rustls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -5844,13 +6058,24 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tower 0.5.2", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", - "windows-registry", + "webpki-roots 1.0.5", +] + +[[package]] +name = "resb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a067ab3b5ca3b4dc307d0de9cf75f9f5e6ca9717b192b2f28a36c83e5de9e76" +dependencies = [ + "potential_utf", + "serde_core", ] [[package]] @@ -5898,14 +6123,16 @@ dependencies = [ [[package]] name = "ron" -version = "0.8.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "base64 0.21.7", "bitflags 2.9.0", + "once_cell", "serde", "serde_derive", + "typeid", + "unicode-ident", ] [[package]] @@ -5930,9 +6157,9 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.34.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" dependencies = [ "bitflags 2.9.0", "fallible-iterator", @@ -6123,6 +6350,7 @@ dependencies = [ "criterion", "deno_ast", "deno_broadcast_channel", + "deno_bundle_runtime", "deno_cache", "deno_console", "deno_core", @@ -6136,7 +6364,7 @@ dependencies = [ "deno_http", "deno_io", "deno_kv", - "deno_media_type", + "deno_media_type 0.2.9", "deno_napi", "deno_net", "deno_node", @@ -6280,7 +6508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "libc", "security-framework-sys", @@ -6317,12 +6545,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -6345,11 +6580,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -6391,9 +6635,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.264.0" +version = "0.285.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34707712f3815e73e1c8319bba06e5bc105bb65fe812ea2e7279ffb905f6312" +checksum = "f26d546283b1182f61e75b598da17f3babde4b25e479a5cb3fbb39d26cb928fd" dependencies = [ "deno_error", "num-bigint", @@ -6560,6 +6804,9 @@ name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +dependencies = [ + "serde", +] [[package]] name = "smartstring" @@ -6667,14 +6914,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "string_enum" +name = "strck" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fe66b8ee349846ce2f9557a26b8f1e74843c4a13fb381f9a3d73617a5f956a" +checksum = "42316e70da376f3d113a68d138a60d8a9883c604fe97942721ec2068dab13a9f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "string_enum" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e" dependencies = [ - "proc-macro2", "quote", - "swc_macros_common 1.0.0", + "swc_macros_common", "syn 2.0.87", ] @@ -6684,35 +6939,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72abeda133c49d7bddece6c154728f83eec8172380c80ab7096da9487e20d27c" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - [[package]] name = "strum" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" dependencies = [ - "strum_macros 0.27.1", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.87", + "strum_macros", ] [[package]] @@ -6736,40 +6969,37 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swc_allocator" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b926f0d94bbb34031fe5449428cfa1268cdc0b31158d6ad9c97e0fc1e79dd" +checksum = "9d7eefd2c8b228a8c73056482b2ae4b3a1071fbe07638e3b55ceca8570cc48bb" dependencies = [ "allocator-api2", "bumpalo", "hashbrown 0.14.5", - "ptr_meta", "rustc-hash 2.1.1", - "triomphe", ] [[package]] name = "swc_atoms" -version = "5.0.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7077ba879f95406459bc0c81f3141c529b34580bc64d7ab7bd15e7118a0391" +checksum = "d4ccbe2ecad10ad7432100f878a107b1d972a8aee83ca53184d00c23a078bb8a" dependencies = [ "hstr", "once_cell", - "rustc-hash 2.1.1", "serde", ] [[package]] name = "swc_common" -version = "9.2.0" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56b6f5a8e5affa271b56757a93badee6f44defcd28f3ba106bb2603afe40d3d" +checksum = "259b675d633a26d24efe3802a9d88858c918e6e8f062d3222d3aa02d56a2cf4c" dependencies = [ "anyhow", "ast_node", "better_scoped_tls", - "cfg-if", + "bytes-str", "either", "from_variant", "new_debug_unreachable", @@ -6778,23 +7008,23 @@ dependencies = [ "rustc-hash 2.1.1", "serde", "siphasher 0.3.11", - "sourcemap", - "swc_allocator", "swc_atoms", "swc_eq_ignore_macros", + "swc_sourcemap", "swc_visit", "tracing", - "unicode-width 0.1.13", + "unicode-width 0.2.2", "url", ] [[package]] name = "swc_config" -version = "3.0.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01bfcbbdea182bdda93713aeecd997749ae324686bf7944f54d128e56be4ea9" +checksum = "72e90b52ee734ded867104612218101722ad87ff4cf74fe30383bd244a533f97" dependencies = [ "anyhow", + "bytes-str", "indexmap 2.9.0", "serde", "serde_json", @@ -6803,21 +7033,21 @@ dependencies = [ [[package]] name = "swc_config_macro" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2ebd37ef52a8555c8c9be78b694d64adcb5e3bc16c928f030d82f1d65fac57" +checksum = "7b416e8ce6de17dc5ea496e10c7012b35bbc0e3fef38d2e065eed936490db0b3" dependencies = [ "proc-macro2", "quote", - "swc_macros_common 1.0.0", + "swc_macros_common", "syn 2.0.87", ] [[package]] name = "swc_ecma_ast" -version = "9.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0613d84468a6bb6d45d13c5a3368b37bd21f3067a089f69adac630dcb462a018" +checksum = "a573a0c72850dec8d4d8085f152d5778af35a2520c3093b242d2d1d50776da7c" dependencies = [ "bitflags 2.9.0", "is-macro", @@ -6825,7 +7055,6 @@ dependencies = [ "once_cell", "phf", "rustc-hash 2.1.1", - "scoped-tls", "serde", "string_enum", "swc_atoms", @@ -6836,9 +7065,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "11.0.0" +version = "20.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01b3de365a86b8f982cc162f257c82f84bda31d61084174a3be37e8ab15c0f4" +checksum = "ff2a6ee1ec49dda8dedeac54e4147b4e8b3f278d9bb34ab28983257a393d34ed" dependencies = [ "ascii", "compact_str", @@ -6847,42 +7076,39 @@ dependencies = [ "once_cell", "regex", "rustc-hash 2.1.1", + "ryu-js", "serde", - "sourcemap", "swc_allocator", "swc_atoms", "swc_common", "swc_ecma_ast", "swc_ecma_codegen_macros", + "swc_sourcemap", "tracing", ] [[package]] name = "swc_ecma_codegen_macros" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99e1931669a67c83e2c2b4375674f6901d1480994a76aa75b23f1389e6c5076" +checksum = "e276dc62c0a2625a560397827989c82a93fd545fcf6f7faec0935a82cc4ddbb8" dependencies = [ "proc-macro2", - "quote", - "swc_macros_common 1.0.0", + "swc_macros_common", "syn 2.0.87", ] [[package]] name = "swc_ecma_lexer" -version = "12.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d11c8e71901401b9aae2ece4946eeb7674b14b8301a53768afbbeeb0e48b599" +checksum = "5e82f7747e052c6ff6e111fa4adeb14e33b46ee6e94fe5ef717601f651db48fc" dependencies = [ - "arrayvec", "bitflags 2.9.0", "either", - "new_debug_unreachable", "num-bigint", - "num-traits", - "phf", "rustc-hash 2.1.1", + "seq-macro", "serde", "smallvec", "smartstring", @@ -6890,15 +7116,15 @@ dependencies = [ "swc_atoms", "swc_common", "swc_ecma_ast", + "swc_ecma_parser", "tracing", - "typed-arena", ] [[package]] name = "swc_ecma_loader" -version = "9.0.0" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb574d660c05f3483c984107452b386e45b95531bdb1253794077edc986f413" +checksum = "fbcababb48f0d46587a0a854b2c577eb3a56fa99687de558338021e93cd2c8f5" dependencies = [ "anyhow", "pathdiff", @@ -6911,45 +7137,38 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "12.0.0" +version = "27.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250786944fbc05f6484eda9213df129ccfe17226ae9ad51b62fce2f72135dbee" +checksum = "7f1a51af1a92cd4904c073b293e491bbc0918400a45d58227b34c961dd6f52d7" dependencies = [ - "arrayvec", "bitflags 2.9.0", "either", - "new_debug_unreachable", "num-bigint", - "num-traits", "phf", "rustc-hash 2.1.1", + "seq-macro", "serde", - "smallvec", "smartstring", "stacker", "swc_atoms", "swc_common", "swc_ecma_ast", - "swc_ecma_lexer", "tracing", - "typed-arena", ] [[package]] name = "swc_ecma_transforms_base" -version = "13.0.0" +version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6856da3da598f4da001b7e4ce225ee8970bc9d5cbaafcaf580190cf0a6031ec5" +checksum = "250f6f165578ca4fee47bd57585c1b9597c94bf4ea6591df47f2b5fa5b1883fe" dependencies = [ "better_scoped_tls", - "bitflags 2.9.0", "indexmap 2.9.0", "once_cell", "par-core", "phf", "rustc-hash 2.1.1", "serde", - "smallvec", "swc_atoms", "swc_common", "swc_ecma_ast", @@ -6961,11 +7180,10 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "13.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f84248f82bad599d250bbcd52cb4db6ff6409f48267fd6f001302a2e9716f80" +checksum = "3d3ab35eff4a980e02d708798ae4c35bc017612292adbffe7b7b554df772fdf5" dependencies = [ - "swc_atoms", "swc_common", "swc_ecma_ast", "swc_ecma_transforms_base", @@ -6975,71 +7193,66 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_macros" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6845dfb88569f3e8cd05901505916a8ebe98be3922f94769ca49f84e8ccec8f7" +checksum = "bc777288799bf6786e5200325a56e4fbabba590264a4a48a0c70b16ad0cf5cd8" dependencies = [ "proc-macro2", "quote", - "swc_macros_common 1.0.0", + "swc_macros_common", "syn 2.0.87", ] [[package]] name = "swc_ecma_transforms_proposal" -version = "13.0.0" +version = "30.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193237e318421ef621c2b3958b4db174770c5280ef999f1878f2df93a2837ca6" +checksum = "c2d7748d4112c87ce1885260035e4a43cebfe7661a40174b7d77a0a04760a257" dependencies = [ "either", "rustc-hash 2.1.1", "serde", - "smallvec", "swc_atoms", "swc_common", "swc_ecma_ast", "swc_ecma_transforms_base", "swc_ecma_transforms_classes", - "swc_ecma_transforms_macros", "swc_ecma_utils", "swc_ecma_visit", ] [[package]] name = "swc_ecma_transforms_react" -version = "15.0.0" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baae39c70229103a72090119887922fc5e32f934f5ca45c0423a5e65dac7e549" +checksum = "03de12e38e47ac1c96ac576f793ad37a9d7b16fbf4f2203881f89152f2498682" dependencies = [ "base64 0.22.1", - "dashmap", + "bytes-str", "indexmap 2.9.0", "once_cell", "rustc-hash 2.1.1", "serde", "sha1", "string_enum", - "swc_allocator", "swc_atoms", "swc_common", "swc_config", "swc_ecma_ast", "swc_ecma_parser", "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", "swc_ecma_utils", "swc_ecma_visit", ] [[package]] name = "swc_ecma_transforms_typescript" -version = "15.0.0" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3c65e0b49f7e2a2bd92f1d89c9a404de27232ce00f6a4053f04bda446d50e5c" +checksum = "4408800fdeb541fabf3659db622189a0aeb386f57b6103f9294ff19dfde4f7b0" dependencies = [ - "once_cell", + "bytes-str", "rustc-hash 2.1.1", - "ryu-js", "serde", "swc_atoms", "swc_common", @@ -7052,15 +7265,14 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "13.1.0" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ed837406d5dbbfbf5792b1dc90964245a0cf659753d4745fe177ffebe8598b9" +checksum = "0fb99e179988cabd473779a4452ab942bcb777176983ca3cbaf22a8f056a65b0" dependencies = [ "indexmap 2.9.0", "num_cpus", "once_cell", "par-core", - "par-iter", "rustc-hash 2.1.1", "ryu-js", "swc_atoms", @@ -7068,14 +7280,13 @@ dependencies = [ "swc_ecma_ast", "swc_ecma_visit", "tracing", - "unicode-id", ] [[package]] name = "swc_ecma_visit" -version = "9.0.0" +version = "18.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "249dc9eede1a4ad59a038f9cfd61ce67845bd2c1392ade3586d714e7181f3c1a" +checksum = "a9611a72a4008d62608547a394e5d72a5245413104db096d95a52368a8cc1d63" dependencies = [ "new_debug_unreachable", "num-bigint", @@ -7088,9 +7299,9 @@ dependencies = [ [[package]] name = "swc_eq_ignore_macros" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" +checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" dependencies = [ "proc-macro2", "quote", @@ -7099,9 +7310,9 @@ dependencies = [ [[package]] name = "swc_macros_common" -version = "0.3.13" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" +checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" dependencies = [ "proc-macro2", "quote", @@ -7109,44 +7320,50 @@ dependencies = [ ] [[package]] -name = "swc_macros_common" -version = "1.0.0" +name = "swc_sourcemap" +version = "9.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" +checksum = "de08ef00f816acdd1a58ee8a81c0e1a59eefef2093aefe5611f256fa6b64c4d7" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", + "base64-simd", + "bitvec", + "bytes-str", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "unicode-id-start", + "url", ] [[package]] name = "swc_visit" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9138b6a36bbe76dd6753c4c0794f7e26480ea757bee499738bedbbb3ae3ec5f3" +checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" dependencies = [ "either", "new_debug_unreachable", ] [[package]] -name = "swc_visit_macros" -version = "0.5.13" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92807d840959f39c60ce8a774a3f83e8193c658068e6d270dbe0a05e40e90b41" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "Inflector", "proc-macro2", "quote", - "swc_macros_common 0.3.13", - "syn 2.0.87", + "unicode-ident", ] [[package]] name = "syn" -version = "1.0.109" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -7154,14 +7371,14 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.87" +name = "syn-match" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "783c4140d7ed89f37116e865b49e5a9fdd28608b9071a9dd1e158b50fc0a31fc" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.87", ] [[package]] @@ -7251,6 +7468,39 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "temporal_capi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a151e402c2bdb6a3a2a2f3f225eddaead2e7ce7dd5d3fa2090deb11b17aa4ed8" +dependencies = [ + "diplomat", + "diplomat-runtime", + "icu_calendar", + "icu_locale", + "num-traits", + "temporal_rs", + "timezone_provider", + "writeable 0.6.2", + "zoneinfo64", +] + +[[package]] +name = "temporal_rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88afde3bd75d2fc68d77a914bece426aa08aa7649ffd0cdd4a11c3d4d33474d1" +dependencies = [ + "core_maths", + "icu_calendar", + "icu_locale", + "ixdtf", + "num-traits", + "timezone_provider", + "tinystr 0.8.2", + "writeable 0.6.2", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -7350,6 +7600,18 @@ dependencies = [ "time-core", ] +[[package]] +name = "timezone_provider" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9ba0000e9e73862f3e7ca1ff159e2ddf915c9d8bb11e38a7874760f445d993" +dependencies = [ + "tinystr 0.8.2", + "zerotrie", + "zerovec 0.11.5", + "zoneinfo64", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -7357,7 +7619,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", - "zerovec", + "zerovec 0.10.4", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec 0.11.5", ] [[package]] @@ -7474,16 +7747,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "futures-util", - "hashbrown 0.14.5", + "hashbrown 0.15.1", "pin-project-lite", "slab", "tokio", @@ -7603,20 +7876,23 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.1" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", "bitflags 2.9.0", "bytes", "futures-core", + "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", + "iri-string", "pin-project-lite", "tokio", "tokio-util", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -7635,9 +7911,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -7647,9 +7923,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -7658,9 +7934,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -7718,10 +7994,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908" [[package]] -name = "typed-arena" -version = "2.0.2" +name = "typeid" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" @@ -7729,47 +8005,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.7.0" @@ -7779,12 +8014,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicode-id" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" - [[package]] name = "unicode-id-start" version = "1.2.0" @@ -7811,9 +8040,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -7845,25 +8074,26 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] name = "urlpattern" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "0f805818f843b548bacc19609eb3619dd2850e54746f5cada37927393c2ef4ec" dependencies = [ + "icu_properties 2.1.2", "regex", "serde", - "unic-ucd-ident", "url", ] @@ -7903,17 +8133,18 @@ dependencies = [ [[package]] name = "v8" -version = "137.3.0" +version = "142.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33995a1fee055ff743281cde33a41f0d618ee0bdbe8bdf6859e11864499c2595" +checksum = "f566072bd76b2631d0dca1d90a766c943863b1fd6b01312281dc919816de976d" dependencies = [ - "bindgen 0.71.1", + "bindgen 0.72.1", "bitflags 2.9.0", "fslock", "gzip-header", "home", "miniz_oxide 0.8.8", "paste", + "temporal_capi", "which 6.0.1", ] @@ -8135,9 +8366,9 @@ dependencies = [ [[package]] name = "web-transport-proto" -version = "0.2.4" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c564e589056437e3a73383f4e0903965879087c9ee0f715e562bbf4e47c08b75" +checksum = "974fa1e325e6cc5327de8887f189a441fcff4f8eedcd31ec87f0ef0cc5283fbc" dependencies = [ "bytes", "http 1.1.0", @@ -8163,19 +8394,32 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "wgpu-core" -version = "24.0.2" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c25545d479b47d3f0a8e373aceb2060b67c6eb841b24ac8c32348151c7a0c" +checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9" dependencies = [ "arrayvec", + "bit-set", "bit-vec", "bitflags 2.9.0", + "bytemuck", "cfg_aliases", "document-features", + "hashbrown 0.16.1", "indexmap 2.9.0", "log", + "macro_rules_attribute", "naga", "once_cell", "parking_lot", @@ -8186,15 +8430,45 @@ dependencies = [ "serde", "smallvec", "thiserror 2.0.14", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" +dependencies = [ + "wgpu-hal", +] + [[package]] name = "wgpu-hal" -version = "24.0.2" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4317a17171dc20e6577bf606796794580accae0716a69edbc7388c86a3ec9f23" +checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef" dependencies = [ "android_system_properties", "arrayvec", @@ -8203,13 +8477,14 @@ dependencies = [ "bitflags 2.9.0", "block", "bytemuck", + "cfg-if", "cfg_aliases", "core-graphics-types", "glow", "glutin_wgl_sys", - "gpu-alloc", "gpu-allocator", "gpu-descriptor", + "hashbrown 0.16.1", "js-sys", "khronos-egl", "libc", @@ -8225,23 +8500,23 @@ dependencies = [ "profiling", "range-alloc", "raw-window-handle", - "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.14", "wasm-bindgen", "web-sys", "wgpu-types", - "windows", - "windows-core", + "windows 0.62.2", + "windows-core 0.62.2", ] [[package]] name = "wgpu-types" -version = "24.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" dependencies = [ "bitflags 2.9.0", + "bytemuck", "js-sys", "log", "serde", @@ -8342,23 +8617,68 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core", + "windows-core 0.58.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -8370,6 +8690,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -8382,14 +8713,30 @@ dependencies = [ ] [[package]] -name = "windows-registry" -version = "0.2.0" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.52.6", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", ] [[package]] @@ -8401,16 +8748,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -8469,6 +8834,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -8605,6 +8979,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "wtf8" version = "0.1.0" @@ -8669,7 +9049,18 @@ checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" dependencies = [ "serde", "stable_deref_trait", - "yoke-derive", + "yoke-derive 0.7.4", + "zerofrom", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive 0.8.1", "zerofrom", ] @@ -8685,6 +9076,18 @@ dependencies = [ "synstructure 0.13.1", ] +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -8696,11 +9099,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.23" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive 0.8.27", ] [[package]] @@ -8716,9 +9119,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -8766,15 +9169,38 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke 0.8.1", + "zerofrom", +] + [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ - "yoke", + "yoke 0.7.4", + "zerofrom", + "zerovec-derive 0.10.3", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "serde", + "yoke 0.8.1", "zerofrom", - "zerovec-derive", + "zerovec-derive 0.11.2", ] [[package]] @@ -8788,6 +9214,30 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "zoneinfo64" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2e5597efbe7c421da8a7fd396b20b571704e787c21a272eecf35dfe9d386f0" +dependencies = [ + "calendrical_calculations", + "icu_locale_core", + "potential_utf", + "resb", + "serde", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index 0aea2a8..dd9837b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ extra_features = ["worker", "snapshot_builder"] # (See [NodePermissions]) node_experimental = [ "deno_node", "deno_resolver", "node_resolver", "deno_npm", "deno_semver", "deno_napi", "deno_runtime", "deno_process", "deno_package_json", + "deno_bundle_runtime", "checksum", "sys_traits", "all_extensions" ] @@ -118,7 +119,7 @@ web_stub = ["webidl", "base64-simd"] web = [ "deno_web", "deno_tls", "deno_fetch", "deno_net", "dep:http", "deno_permissions", "deno_telemetry", "deno_fs", "webidl", "console", "url", "crypto", "url_import", "fs_import", - "hyper-util", "rustls" + "hyper-util", "rustls", "sys_traits" ] # [https://gpuweb.github.io/gpuweb/] @@ -168,12 +169,12 @@ async-trait = "^0.1.89" paste = "1.0.15" # The deno runtime itself, and the webidl extension for the web APIs -deno_core = "^0.355.0" -deno_error = "=0.7.0" -deno_features = "0.10.0" +deno_core = "^0.376.0" +deno_error = "=0.7.1" +deno_features = "^0.23.0" # For transpiling typescript -deno_ast = { version = "=0.49.0", features = ["transpiling", "cjs"] } +deno_ast = { version = "^0.52.0", features = ["transpiling", "cjs"] } deno_media_type = { version = "=0.2.9", features = ["module_specifier"] } # Runtime for async tasks @@ -185,40 +186,41 @@ hyper-util = {version = "^0.1.10", optional = true} rustls = {version = "0.23.28", optional = true} # For URL imports -# Pinned for now due to upstream issues -reqwest = { version = "=0.12.8", optional = true, default-features = false, features = ["blocking", "rustls-tls"] } +# Upgraded to support axum 0.8+ +reqwest = { version = "^0.12.20", optional = true, default-features = false, features = ["blocking", "rustls-tls"] } http = { version = "^1.0", optional = true } -deno_permissions = { version = "^0.72.0", optional = true } +deno_permissions = { version = "^0.85.0", optional = true } # # Dependencies for the various extension features # -deno_broadcast_channel = { version = "^0.207.0", optional = true } - -deno_cache = { version = "^0.145.0", optional = true } -deno_console = { version = "^0.213.0", optional = true } -deno_cron = { version = "^0.93.0", optional = true } -deno_crypto = { version = "^0.227.0", optional = true } -deno_fetch = { version = "^0.237.0", optional = true } -deno_ffi = { version = "^0.200.0", optional = true } -deno_fs = { version = "^0.123.0", optional = true, features = ["sync_fs"] } -deno_http = { version = "^0.211.0", optional = true } -deno_kv = { version = "^0.121.0", optional = true } -deno_net = { version = "^0.205.0", optional = true } -deno_node = { version = "^0.151.0", optional = true } -deno_tls = { version = "^0.200.0", optional = true } -deno_url = { version = "^0.213.0", optional = true } - -deno_web = { version = "^0.244.0", optional = true } -deno_webidl = { version = "^0.213.0", optional = true } -deno_webstorage = { version = "^0.208.0", optional = true } -deno_websocket = { version = "^0.218.0", optional = true } -deno_webgpu = { version = "^0.180.0", optional = true } - -deno_io = { version = "^0.123.0", optional = true } -deno_telemetry = { version = "^0.35.0", optional = true } +deno_broadcast_channel = { version = "^0.216.0", optional = true } + +deno_bundle_runtime = { version = "^0.13.0", optional = true } +deno_cache = { version = "^0.159.0", optional = true } +deno_console = { version = "^0.222.0", optional = true } +deno_cron = { version = "^0.106.0", optional = true } +deno_crypto = { version = "^0.240.0", optional = true } +deno_fetch = { version = "^0.250.0", optional = true } +deno_ffi = { version = "^0.213.0", optional = true } +deno_fs = { version = "^0.136.0", optional = true, features = ["sync_fs"] } +deno_http = { version = "^0.224.0", optional = true } +deno_kv = { version = "^0.134.0", optional = true } +deno_net = { version = "^0.218.0", optional = true } +deno_node = { version = "^0.164.0", optional = true } +deno_tls = { version = "^0.213.0", optional = true } +deno_url = { version = "^0.222.0", optional = true } + +deno_web = { version = "^0.257.0", optional = true } +deno_webidl = { version = "^0.226.0", optional = true } +deno_webstorage = { version = "^0.221.0", optional = true } +deno_websocket = { version = "^0.231.0", optional = true } +deno_webgpu = { version = "^0.193.0", optional = true } + +deno_io = { version = "^0.136.0", optional = true } +deno_telemetry = { version = "^0.48.0", optional = true } # Dependencies for the IO feature rustyline = {version = "=13.0.0", optional = true} @@ -235,17 +237,17 @@ once_cell = {version = "^1.17.1", optional = true} base64-simd = {version = "0.8.0", optional = true} # Dependencies for the node feature -deno_resolver = { version = "^0.44.0", optional = true } -node_resolver = { version = "^0.51.0", optional = true, features = ["sync"] } -deno_runtime = { version = "^0.221.0", optional = true, features = ["exclude_runtime_main_js"] } -deno_terminal = { version = "=0.2.2", optional = true } -deno_semver = { version = "=0.9.0", optional = true } -deno_napi = { version = "^0.144.0", optional = true } -deno_npm = { version = "=0.36.0", optional = true } -deno_process = { version = "^0.28.0", optional = true } -deno_package_json = { version = "=0.15.0", optional = true } +deno_resolver = { version = "^0.57.0", optional = true } +node_resolver = { version = "^0.64.0", optional = true, features = ["sync"] } +deno_runtime = { version = "^0.234.0", optional = true, features = ["exclude_runtime_main_js"] } +deno_terminal = { version = "^0.2.3", optional = true } +deno_semver = { version = "^0.9.1", optional = true } +deno_napi = { version = "^0.157.0", optional = true } +deno_npm = { version = "^0.42.2", optional = true } +deno_process = { version = "^0.41.0", optional = true } +deno_package_json = { version = "^0.28.0", optional = true } checksum = { version = "0.2.1", optional = true } -sys_traits = { version = "=0.1.17", optional = true } +sys_traits = { version = "=0.1.17", optional = true, features = ["libc", "real", "winapi"] } [dev-dependencies] version-sync = "0.9.5" diff --git a/examples/custom_import_logic.rs b/examples/custom_import_logic.rs index 4757bd5..d55ba4a 100644 --- a/examples/custom_import_logic.rs +++ b/examples/custom_import_logic.rs @@ -78,7 +78,6 @@ impl ImportProvider for MyImportProvider { specifier: &ModuleSpecifier, _referrer: Option<&ModuleSpecifier>, _is_dyn_import: bool, - _requested_module_type: deno_core::RequestedModuleType, ) -> Option> { match specifier.scheme() { // @@ -102,7 +101,10 @@ impl ImportProvider for MyImportProvider { fn main() -> Result<(), rustyscript::Error> { let mut import_provider = MyImportProvider::default(); - import_provider.add_redirect("mod_assert", "https://deno.land/std@0.224.0/assert/mod.ts")?; + import_provider.add_redirect( + "mod_assert", + "https://deno.land/std@0.224.0/assert/assert.ts", + )?; import_provider.add_static_module("my-module", "export const foo = 1"); let mut runtime = Runtime::new(RuntimeOptions { @@ -113,10 +115,10 @@ fn main() -> Result<(), rustyscript::Error> { let module = Module::new( "custom_imports.js", " - import { assertEquals } from 'redirect:mod_assert'; + import { assert } from 'redirect:mod_assert'; import { foo } from 'static:my-module'; - assertEquals(1, foo) + assert(foo === 1); ", ); diff --git a/examples/module_loader_cache.rs b/examples/module_loader_cache.rs index 9cb8e58..6e879d8 100644 --- a/examples/module_loader_cache.rs +++ b/examples/module_loader_cache.rs @@ -43,7 +43,6 @@ impl ImportProvider for MemoryCache { specifier: &ModuleSpecifier, _referrer: Option<&ModuleSpecifier>, _is_dyn_import: bool, - _requested_module_type: deno_core::RequestedModuleType, ) -> Option> { // Return the source code if the module is in the cache self.get(specifier).map(Ok) diff --git a/src/async_bridge.rs b/src/async_bridge.rs index 7d3204a..ffbbe6b 100644 --- a/src/async_bridge.rs +++ b/src/async_bridge.rs @@ -29,8 +29,8 @@ impl TokioRuntime { /// /// This runs the given future on the current thread, blocking until it is complete, and yielding its resolved result. Any tasks or timers which the future spawns internally will be executed on the runtime. /// - /// When this is used on a `current_thread` runtime, only the [`Runtime::block_on`] method can drive the IO and timer drivers, but the `Handle::block_on` method cannot drive them. - /// This means that, when using this method on a `current_thread` runtime, anything that relies on IO or timers will not work unless there is another thread currently calling [`Runtime::block_on`] on the same runtime. + /// When this is used on a `current_thread` runtime, only the [`tokio::runtime::Runtime::block_on`] method can drive the IO and timer drivers, but the `Handle::block_on` method cannot drive them. + /// This means that, when using this method on a `current_thread` runtime, anything that relies on IO or timers will not work unless there is another thread currently calling [`tokio::runtime::Runtime::block_on`] on the same runtime. pub fn block_on(&self, f: F) -> T where F: std::future::Future, diff --git a/src/error.rs b/src/error.rs index 49eb51c..7a4eeea 100644 --- a/src/error.rs +++ b/src/error.rs @@ -107,6 +107,12 @@ impl From for Error { } } +impl From> for Error { + fn from(err: Box) -> Self { + Self::JsError(err) + } +} + impl Error { /// Formats an error for display in a terminal /// If the error is a `JsError`, it will attempt to highlight the source line @@ -239,7 +245,7 @@ map_error!(deno_ast::TranspileError, |e| Error::Runtime(e.to_string())); map_error!(deno_core::error::CoreError, |e| { let e = e.into_kind(); match e { - CoreErrorKind::Js(js_error) => Error::JsError(Box::new(js_error)), + CoreErrorKind::Js(js_error) => Error::JsError(js_error), _ => Error::Runtime(e.to_string()), } }); @@ -273,10 +279,7 @@ map_error!(deno_core::futures::channel::oneshot::Canceled, |e| { Error::Timeout(e.to_string()) }); -#[cfg(feature = "broadcast_channel")] -map_error!(deno_broadcast_channel::BroadcastChannelError, |e| { - Error::Runtime(e.to_string()) -}); +// Note: BroadcastChannelError mapping removed - no longer exported from deno_web #[cfg(test)] mod test { diff --git a/src/ext/broadcast_channel/01_broadcast_channel.js b/src/ext/broadcast_channel/01_broadcast_channel.js new file mode 100644 index 0000000..fce6882 --- /dev/null +++ b/src/ext/broadcast_channel/01_broadcast_channel.js @@ -0,0 +1 @@ +export * from "ext:deno_web/01_broadcast_channel.js"; diff --git a/src/ext/broadcast_channel/mod.rs b/src/ext/broadcast_channel/mod.rs index b9e84f4..c34c47a 100644 --- a/src/ext/broadcast_channel/mod.rs +++ b/src/ext/broadcast_channel/mod.rs @@ -1,10 +1,11 @@ -use deno_broadcast_channel::InMemoryBroadcastChannel; use deno_core::{extension, Extension}; +use deno_web::InMemoryBroadcastChannel; use super::ExtensionTrait; -mod wrapper; -pub use wrapper::BroadcastChannelWrapper; +// Note: The BroadcastChannelWrapper has been removed due to upstream API changes +// in deno_web. The broadcast channel methods are now private and only accessible +// through JavaScript ops. Use the JavaScript BroadcastChannel API directly. extension!( init_broadcast_channel, @@ -12,68 +13,30 @@ extension!( esm_entry_point = "ext:init_broadcast_channel/init_broadcast_channel.js", esm = [ dir "src/ext/broadcast_channel", "init_broadcast_channel.js" ], ); + +extension!( + deno_broadcast_channel, + deps = [deno_web], + esm = [ dir "src/ext/broadcast_channel", "01_broadcast_channel.js" ], +); + impl ExtensionTrait<()> for init_broadcast_channel { fn init((): ()) -> Extension { init_broadcast_channel::init() } } -impl ExtensionTrait for deno_broadcast_channel::deno_broadcast_channel { - fn init(channel: InMemoryBroadcastChannel) -> Extension { - deno_broadcast_channel::deno_broadcast_channel::init(channel) + +impl ExtensionTrait<()> for deno_broadcast_channel { + fn init((): ()) -> Extension { + deno_broadcast_channel::init() } } -pub fn extensions(channel: InMemoryBroadcastChannel, is_snapshot: bool) -> Vec { +// Note: broadcast_channel functionality is now integrated into deno_web +// No separate initialization is needed as it's handled by deno_web extension +pub fn extensions(_channel: InMemoryBroadcastChannel, is_snapshot: bool) -> Vec { vec![ - deno_broadcast_channel::deno_broadcast_channel::build(channel, is_snapshot), + deno_broadcast_channel::build((), is_snapshot), init_broadcast_channel::build((), is_snapshot), ] } - -#[cfg(test)] -mod test { - use deno_core::PollEventLoopOptions; - - use crate::{module, BroadcastChannelWrapper, Module, Runtime, RuntimeOptions}; - - static TEST_MOD: Module = module!( - "test.js", - " - const channel = new BroadcastChannel('my_channel'); - channel.onmessage = (event) => { - channel.postMessage('Received: ' + event.data); - }; - " - ); - - #[test] - fn test_broadcast_channel() { - let options = RuntimeOptions::default(); - let channel = options.extension_options.broadcast_channel.clone(); - - let mut runtime = Runtime::new(options).unwrap(); - let tokio_runtime = runtime.tokio_runtime(); - - let channel = BroadcastChannelWrapper::new(&channel, "my_channel").unwrap(); - - tokio_runtime - .block_on(runtime.load_module_async(&TEST_MOD)) - .unwrap(); - - channel.send_sync(&mut runtime, "foo").unwrap(); - - runtime - .block_on_event_loop( - PollEventLoopOptions::default(), - Some(std::time::Duration::from_secs(1)), - ) - .unwrap(); - - let value = channel - .recv_sync::(&mut runtime, Some(std::time::Duration::from_secs(1))) - .unwrap() - .unwrap(); - - assert_eq!(value, "Received: foo"); - } -} diff --git a/src/ext/broadcast_channel/wrapper.rs b/src/ext/broadcast_channel/wrapper.rs deleted file mode 100644 index 36cefce..0000000 --- a/src/ext/broadcast_channel/wrapper.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::time::Duration; - -use deno_broadcast_channel::BroadcastChannel; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::{big_json_args, Error, Runtime}; - -/// Helper struct to wrap a broadcast channel -/// Takes care of some of the boilerplate for serialization/deserialization -pub struct BroadcastChannelWrapper { - channel: Channel, - resource: ::Resource, - name: String, -} -impl BroadcastChannelWrapper { - /// Create a new broadcast channel wrapper and subscribe to the channel - /// Unsubscribe is called when the wrapper is dropped - /// - /// # Errors - /// Will return an error if the channel cannot be subscribed to - pub fn new(channel: &Channel, name: impl ToString) -> Result { - let channel = channel.clone(); - let resource = channel.subscribe()?; - let name = name.to_string(); - Ok(Self { - channel, - resource, - name, - }) - } - - /// Send a message to the channel, blocking until the message is sent - /// - /// # Errors - /// Will return an error if the message cannot be serialized or sent - pub fn send_sync(&self, runtime: &mut Runtime, data: T) -> Result<(), Error> { - let tokio_rt = runtime.tokio_runtime(); - tokio_rt.block_on(self.send(runtime, data)) - } - - /// Send a message to the channel - /// - /// # Errors - /// Will return an error if the message cannot be serialized or sent - pub async fn send(&self, runtime: &mut Runtime, data: T) -> Result<(), Error> { - let data: Vec = runtime - .call_function_async(None, "broadcast_serialize", &data) - .await?; - self.channel - .send(&self.resource, self.name.clone(), data) - .await?; - Ok(()) - } - - /// Receive a message from the channel, waiting for a message to arrive, or until the timeout is reached - /// - /// # Errors - /// Will return an error if the message cannot be deserialized - /// or if receiving the message fails - pub async fn recv( - &self, - runtime: &mut Runtime, - timeout: Option, - ) -> Result, Error> { - let msg = if let Some(timeout) = timeout { - tokio::select! { - msg = self.channel.recv(&self.resource) => msg, - () = tokio::time::sleep(timeout) => Ok(None), - } - } else { - self.channel.recv(&self.resource).await - }?; - - let Some((name, data)) = msg else { - return Ok(None); - }; - - if name == self.name { - let data: T = runtime - .call_function_async(None, "broadcast_deserialize", big_json_args!(data)) - .await?; - Ok(Some(data)) - } else { - Ok(None) - } - } - - /// Receive a message from the channel, blocking until a message arrives, or until the timeout is reached - /// - /// # Errors - /// Will return an error if the message cannot be deserialized - /// or if receiving the message fails - pub fn recv_sync( - &self, - runtime: &mut Runtime, - timeout: Option, - ) -> Result, Error> { - let tokio_rt = runtime.tokio_runtime(); - tokio_rt.block_on(self.recv(runtime, timeout)) - } -} - -impl Drop for BroadcastChannelWrapper { - fn drop(&mut self) { - self.channel.unsubscribe(&self.resource).ok(); - } -} diff --git a/src/ext/console/init_console.js b/src/ext/console/init_console.js index 13aede7..353a0bb 100644 --- a/src/ext/console/init_console.js +++ b/src/ext/console/init_console.js @@ -1,10 +1,38 @@ -import * as _console from 'ext:deno_console/01_console.js'; +import * as _console from 'ext:deno_web/01_console.js'; -import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; -applyToGlobal({ - console: nonEnumerable( - new _console.Console((msg, level) => globalThis.Deno.core.print(msg, level > 1)), - ), -}); +import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; + +const consoleInstance = new _console.Console((msg, level) => + globalThis.Deno.core.print(msg, level > 1), +); +for (const name of [ + "log", + "debug", + "info", + "warn", + "error", + "dir", + "dirxml", + "assert", + "clear", + "count", + "countReset", + "group", + "groupCollapsed", + "groupEnd", + "table", + "time", + "timeEnd", + "timeLog", + "trace", +]) { + const value = consoleInstance[name]; + if (typeof value === "function") { + consoleInstance[name] = value.bind(consoleInstance); + } +} +applyToGlobal({ + console: nonEnumerable(consoleInstance), +}); -globalThis.Deno.inspect = _console.inspect; \ No newline at end of file +globalThis.Deno.inspect = _console.inspect; diff --git a/src/ext/console/mod.rs b/src/ext/console/mod.rs index c3517fe..60013a0 100644 --- a/src/ext/console/mod.rs +++ b/src/ext/console/mod.rs @@ -1,28 +1,19 @@ -use deno_core::{extension, Extension}; - -use super::ExtensionTrait; - -extension!( - init_console, - deps = [rustyscript], - esm_entry_point = "ext:init_console/init_console.js", - esm = [ dir "src/ext/console", "init_console.js" ], -); -impl ExtensionTrait<()> for init_console { - fn init((): ()) -> Extension { - deno_terminal::colors::set_use_color(true); - init_console::init() - } -} -impl ExtensionTrait<()> for deno_console::deno_console { - fn init((): ()) -> Extension { - deno_console::deno_console::init() - } -} - -pub fn extensions(is_snapshot: bool) -> Vec { - vec![ - deno_console::deno_console::build((), is_snapshot), - init_console::build((), is_snapshot), - ] -} +use deno_core::{extension, Extension}; + +use super::ExtensionTrait; + +extension!( + init_console, + deps = [rustyscript, deno_web], + esm_entry_point = "ext:init_console/init_console.js", + esm = [ dir "src/ext/console", "init_console.js" ], +); +impl ExtensionTrait<()> for init_console { + fn init((): ()) -> Extension { + deno_terminal::colors::set_use_color(true); + init_console::init() + } +} +pub fn extensions(is_snapshot: bool) -> Vec { + vec![>::build((), is_snapshot)] +} diff --git a/src/ext/ffi/mod.rs b/src/ext/ffi/mod.rs index a7b90fa..a1787b3 100644 --- a/src/ext/ffi/mod.rs +++ b/src/ext/ffi/mod.rs @@ -1,8 +1,6 @@ -use std::{borrow::Cow, path::Path}; - use deno_core::{extension, Extension}; -use super::{web::PermissionsContainer, ExtensionTrait}; +use super::ExtensionTrait; extension!( init_ffi, @@ -10,14 +8,16 @@ extension!( esm_entry_point = "ext:init_ffi/init_ffi.js", esm = [ dir "src/ext/ffi", "init_ffi.js" ], ); + impl ExtensionTrait<()> for init_ffi { fn init((): ()) -> Extension { init_ffi::init() } } + impl ExtensionTrait<()> for deno_ffi::deno_ffi { fn init((): ()) -> Extension { - deno_ffi::deno_ffi::init::(None) + deno_ffi::deno_ffi::init(None) } } @@ -27,19 +27,3 @@ pub fn extensions(is_snapshot: bool) -> Vec { init_ffi::build((), is_snapshot), ] } - -impl deno_ffi::FfiPermissions for PermissionsContainer { - fn check_partial_no_path(&mut self) -> Result<(), deno_permissions::PermissionCheckError> { - self.0.check_exec()?; - Ok(()) - } - - fn check_partial_with_path<'a>( - &mut self, - path: Cow<'a, Path>, - ) -> Result, deno_permissions::PermissionCheckError> { - self.check_partial_no_path()?; - let p = self.0.check_read(path, None)?; - Ok(p) - } -} diff --git a/src/ext/fs/mod.rs b/src/ext/fs/mod.rs index 2831a45..26db495 100644 --- a/src/ext/fs/mod.rs +++ b/src/ext/fs/mod.rs @@ -1,10 +1,7 @@ -use std::{borrow::Cow, path::Path}; - use deno_core::{extension, Extension}; use deno_fs::FileSystemRc; -use deno_permissions::{CheckedPath, PermissionCheckError, PermissionDeniedError}; -use super::{web::PermissionsContainer, ExtensionTrait}; +use super::ExtensionTrait; extension!( init_fs, @@ -12,14 +9,16 @@ extension!( esm_entry_point = "ext:init_fs/init_fs.js", esm = [ dir "src/ext/fs", "init_fs.js" ], ); + impl ExtensionTrait<()> for init_fs { fn init((): ()) -> Extension { init_fs::init() } } + impl ExtensionTrait for deno_fs::deno_fs { fn init(fs: FileSystemRc) -> Extension { - deno_fs::deno_fs::init::(fs) + deno_fs::deno_fs::init(fs) } } @@ -29,65 +28,3 @@ pub fn extensions(fs: FileSystemRc, is_snapshot: bool) -> Vec { init_fs::build((), is_snapshot), ] } - -impl deno_fs::FsPermissions for PermissionsContainer { - fn check_open<'a>( - &self, - path: Cow<'a, Path>, - access_kind: deno_permissions::OpenAccessKind, - api_name: &str, - ) -> Result, PermissionCheckError> { - let read = access_kind.is_read(); - let write = access_kind.is_write(); - - let p = self.0.check_open(true, read, write, path, api_name).ok_or( - PermissionCheckError::PermissionDenied(PermissionDeniedError { - access: api_name.to_string(), - name: "open", - }), - )?; - - Ok(CheckedPath::unsafe_new(p)) - } - - fn check_open_blind<'a>( - &self, - path: Cow<'a, Path>, - access_kind: deno_permissions::OpenAccessKind, - display: &str, - api_name: &str, - ) -> Result, PermissionCheckError> { - if access_kind.is_read() { - self.0.check_read_all(Some(api_name))?; - self.0.check_read_blind(&path, display, api_name)?; - } - - if access_kind.is_write() { - self.0.check_write_all(api_name)?; - self.0.check_write_blind(&path, display, api_name)?; - } - - Ok(CheckedPath::unsafe_new(path)) - } - - fn check_read_all(&self, api_name: &str) -> Result<(), PermissionCheckError> { - self.0.check_read_all(Some(api_name))?; - Ok(()) - } - - fn check_write_partial<'a>( - &self, - path: Cow<'a, Path>, - api_name: &str, - ) -> Result, PermissionCheckError> { - self.0.check_write_all(api_name)?; - let p = self.0.check_write_partial(path, api_name)?; - - Ok(CheckedPath::unsafe_new(p)) - } - - fn check_write_all(&self, api_name: &str) -> Result<(), PermissionCheckError> { - self.0.check_write_all(api_name)?; - Ok(()) - } -} diff --git a/src/ext/io/tty_unix.rs b/src/ext/io/tty_unix.rs index f17dba7..cd687fc 100644 --- a/src/ext/io/tty_unix.rs +++ b/src/ext/io/tty_unix.rs @@ -203,7 +203,7 @@ fn console_size_from_fd(fd: std::os::unix::prelude::RawFd) -> Result for init_kv { - fn init((): ()) -> Extension { - init_kv::init() - } -} -impl ExtensionTrait for deno_kv::deno_kv { - fn init(store: KvStore) -> Extension { - deno_kv::deno_kv::init(store.handler(), store.config()) - } -} - -pub fn extensions(store: KvStore, is_snapshot: bool) -> Vec { - vec![ - deno_kv::deno_kv::build(store, is_snapshot), - init_kv::build((), is_snapshot), - ] -} - -#[derive(Clone)] -enum KvStoreBuilder { - Local { - path: Option, - rng_seed: Option, - }, - - Remote { - http_options: deno_kv::remote::HttpOptions, - }, -} - -/// Configuration for the key-value store -/// -/// Needed due to limitations in the deno implementation -#[derive(Clone, Copy)] -#[allow(clippy::struct_field_names)] -pub struct KvConfig { - /// Maximum size of a key in bytes - pub max_write_key_size_bytes: usize, - - /// Maximum size of a value in bytes - pub max_value_size_bytes: usize, - - /// Maximum number of ranges in a read request - pub max_read_ranges: usize, - - /// Maximum number of entries in a read request - pub max_read_entries: usize, - - /// Maximum number of checks in a read request - pub max_checks: usize, - - /// Maximum number of mutations in a write request - pub max_mutations: usize, - - /// Maximum number of watched keys - pub max_watched_keys: usize, - - /// Maximum size of a mutation in bytes - pub max_total_mutation_size_bytes: usize, - - /// Maximum size of a key in bytes - pub max_total_key_size_bytes: usize, -} -impl From for deno_kv::KvConfig { - fn from(value: KvConfig) -> Self { - deno_kv::KvConfigBuilder::default() - .max_write_key_size_bytes(value.max_write_key_size_bytes) - .max_value_size_bytes(value.max_value_size_bytes) - .max_read_ranges(value.max_read_ranges) - .max_read_entries(value.max_read_entries) - .max_checks(value.max_checks) - .max_mutations(value.max_mutations) - .max_watched_keys(value.max_watched_keys) - .max_total_mutation_size_bytes(value.max_total_mutation_size_bytes) - .max_total_key_size_bytes(value.max_total_key_size_bytes) - .build() - } -} -impl Default for KvConfig { - fn default() -> Self { - const MAX_WRITE_KEY_SIZE_BYTES: usize = 2048; - const MAX_VALUE_SIZE_BYTES: usize = 65536; - const MAX_READ_RANGES: usize = 10; - const MAX_READ_ENTRIES: usize = 1000; - const MAX_CHECKS: usize = 100; - const MAX_MUTATIONS: usize = 1000; - const MAX_WATCHED_KEYS: usize = 10; - const MAX_TOTAL_MUTATION_SIZE_BYTES: usize = 800 * 1024; - const MAX_TOTAL_KEY_SIZE_BYTES: usize = 80 * 1024; - - KvConfig { - max_write_key_size_bytes: MAX_WRITE_KEY_SIZE_BYTES, - max_value_size_bytes: MAX_VALUE_SIZE_BYTES, - max_read_ranges: MAX_READ_RANGES, - max_read_entries: MAX_READ_ENTRIES, - max_checks: MAX_CHECKS, - max_mutations: MAX_MUTATIONS, - max_watched_keys: MAX_WATCHED_KEYS, - max_total_mutation_size_bytes: MAX_TOTAL_MUTATION_SIZE_BYTES, - max_total_key_size_bytes: MAX_TOTAL_KEY_SIZE_BYTES, - } - } -} - -/// Bi-modal key-value store for deno -/// -/// Wraps the deno sqlite (local) and remote implementations -#[derive(Clone)] -pub struct KvStore(KvStoreBuilder, KvConfig); -impl KvStore { - /// Create a new local key-value store - /// - /// Sqlite backend - #[must_use] - pub fn new_local(path: Option, rng_seed: Option, config: KvConfig) -> Self { - Self(KvStoreBuilder::Local { path, rng_seed }, config) - } - - /// Create a new remote key-value store - /// - /// Remote backend - #[must_use] - pub fn new_remote(http_options: deno_kv::remote::HttpOptions, config: KvConfig) -> Self { - Self(KvStoreBuilder::Remote { http_options }, config) - } - - /// Get the handler for the key-value store - /// - /// This is used to create the extension - #[must_use] - pub fn handler(&self) -> MultiBackendDbHandler { - match &self.0 { - KvStoreBuilder::Local { path, rng_seed } => { - let db = SqliteDbHandler::::new(path.clone(), *rng_seed); - MultiBackendDbHandler::new(vec![(&[""], Box::new(db))]) - } - - KvStoreBuilder::Remote { http_options } => { - let db = RemoteDbHandler::::new(http_options.clone()); - MultiBackendDbHandler::new(vec![(&["https://", "http://"], Box::new(db))]) - } - } - } - - /// Get the configuration for the key-value store - /// - /// Converts the local configuration to the deno configuration - /// - /// Since that one lacks public fields, or clone - #[must_use] - pub fn config(&self) -> deno_kv::KvConfig { - self.1.into() - } -} -impl Default for KvStore { - fn default() -> Self { - Self::new_local(None, None, KvConfig::default()) - } -} - -impl SqliteDbHandlerPermissions for PermissionsContainer { - fn check_open<'a>( - &mut self, - path: Cow<'a, std::path::Path>, - open_access: deno_permissions::OpenAccessKind, - api_name: &str, - ) -> Result, deno_permissions::PermissionCheckError> { - let read = open_access.is_read(); - let write = open_access.is_write(); - - let p = self.0.check_open(true, read, write, path, api_name).ok_or( - PermissionCheckError::PermissionDenied(PermissionDeniedError { - access: api_name.to_string(), - name: "open", - }), - )?; - - Ok(CheckedPath::unsafe_new(p)) - } -} - -impl RemoteDbHandlerPermissions for PermissionsContainer { - fn check_env(&mut self, var: &str) -> Result<(), deno_permissions::PermissionCheckError> { - self.0.check_env(var)?; - Ok(()) - } - - fn check_net_url( - &mut self, - url: &reqwest::Url, - api_name: &str, - ) -> Result<(), deno_permissions::PermissionCheckError> { - self.0.check_url(url, api_name)?; - Ok(()) - } -} +use std::path::PathBuf; + +use deno_core::{extension, Extension}; +use deno_kv::{dynamic::MultiBackendDbHandler, remote::RemoteDbHandler, sqlite::SqliteDbHandler}; + +use super::ExtensionTrait; + +extension!( + init_kv, + deps = [rustyscript], + esm_entry_point = "ext:init_kv/init_kv.js", + esm = [ dir "src/ext/kv", "init_kv.js" ], +); +impl ExtensionTrait<()> for init_kv { + fn init((): ()) -> Extension { + init_kv::init() + } +} +impl ExtensionTrait for deno_kv::deno_kv { + fn init(store: KvStore) -> Extension { + deno_kv::deno_kv::init(store.handler(), store.config()) + } +} + +pub fn extensions(store: KvStore, is_snapshot: bool) -> Vec { + vec![ + deno_kv::deno_kv::build(store, is_snapshot), + init_kv::build((), is_snapshot), + ] +} + +#[derive(Clone)] +enum KvStoreBuilder { + Local { + path: Option, + rng_seed: Option, + }, + + Remote { + http_options: deno_kv::remote::HttpOptions, + }, +} + +/// Configuration for the key-value store +/// +/// Needed due to limitations in the deno implementation +#[derive(Clone, Copy)] +#[allow(clippy::struct_field_names)] +pub struct KvConfig { + /// Maximum size of a key in bytes + pub max_write_key_size_bytes: usize, + + /// Maximum size of a value in bytes + pub max_value_size_bytes: usize, + + /// Maximum number of ranges in a read request + pub max_read_ranges: usize, + + /// Maximum number of entries in a read request + pub max_read_entries: usize, + + /// Maximum number of checks in a read request + pub max_checks: usize, + + /// Maximum number of mutations in a write request + pub max_mutations: usize, + + /// Maximum number of watched keys + pub max_watched_keys: usize, + + /// Maximum size of a mutation in bytes + pub max_total_mutation_size_bytes: usize, + + /// Maximum size of a key in bytes + pub max_total_key_size_bytes: usize, +} +impl From for deno_kv::KvConfig { + fn from(value: KvConfig) -> Self { + deno_kv::KvConfigBuilder::default() + .max_write_key_size_bytes(value.max_write_key_size_bytes) + .max_value_size_bytes(value.max_value_size_bytes) + .max_read_ranges(value.max_read_ranges) + .max_read_entries(value.max_read_entries) + .max_checks(value.max_checks) + .max_mutations(value.max_mutations) + .max_watched_keys(value.max_watched_keys) + .max_total_mutation_size_bytes(value.max_total_mutation_size_bytes) + .max_total_key_size_bytes(value.max_total_key_size_bytes) + .build() + } +} +impl Default for KvConfig { + fn default() -> Self { + const MAX_WRITE_KEY_SIZE_BYTES: usize = 2048; + const MAX_VALUE_SIZE_BYTES: usize = 65536; + const MAX_READ_RANGES: usize = 10; + const MAX_READ_ENTRIES: usize = 1000; + const MAX_CHECKS: usize = 100; + const MAX_MUTATIONS: usize = 1000; + const MAX_WATCHED_KEYS: usize = 10; + const MAX_TOTAL_MUTATION_SIZE_BYTES: usize = 800 * 1024; + const MAX_TOTAL_KEY_SIZE_BYTES: usize = 80 * 1024; + + KvConfig { + max_write_key_size_bytes: MAX_WRITE_KEY_SIZE_BYTES, + max_value_size_bytes: MAX_VALUE_SIZE_BYTES, + max_read_ranges: MAX_READ_RANGES, + max_read_entries: MAX_READ_ENTRIES, + max_checks: MAX_CHECKS, + max_mutations: MAX_MUTATIONS, + max_watched_keys: MAX_WATCHED_KEYS, + max_total_mutation_size_bytes: MAX_TOTAL_MUTATION_SIZE_BYTES, + max_total_key_size_bytes: MAX_TOTAL_KEY_SIZE_BYTES, + } + } +} + +/// Bi-modal key-value store for deno +/// +/// Wraps the deno sqlite (local) and remote implementations +#[derive(Clone)] +pub struct KvStore(KvStoreBuilder, KvConfig); +impl KvStore { + /// Create a new local key-value store + /// + /// Sqlite backend + #[must_use] + pub fn new_local(path: Option, rng_seed: Option, config: KvConfig) -> Self { + Self(KvStoreBuilder::Local { path, rng_seed }, config) + } + + /// Create a new remote key-value store + /// + /// Remote backend + #[must_use] + pub fn new_remote(http_options: deno_kv::remote::HttpOptions, config: KvConfig) -> Self { + Self(KvStoreBuilder::Remote { http_options }, config) + } + + /// Get the handler for the key-value store + /// + /// This is used to create the extension + #[must_use] + pub fn handler(&self) -> MultiBackendDbHandler { + match &self.0 { + KvStoreBuilder::Local { path, rng_seed } => { + let db = SqliteDbHandler::new(path.clone(), *rng_seed); + MultiBackendDbHandler::new(vec![(&[""], Box::new(db))]) + } + + KvStoreBuilder::Remote { http_options } => { + let db = RemoteDbHandler::new(http_options.clone()); + MultiBackendDbHandler::new(vec![(&["https://", "http://"], Box::new(db))]) + } + } + } + + /// Get the configuration for the key-value store + /// + /// Converts the local configuration to the deno configuration + /// + /// Since that one lacks public fields, or clone + #[must_use] + pub fn config(&self) -> deno_kv::KvConfig { + self.1.into() + } +} +impl Default for KvStore { + fn default() -> Self { + Self::new_local(None, None, KvConfig::default()) + } +} diff --git a/src/ext/mod.rs b/src/ext/mod.rs index ff85fa4..3a5fba9 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -139,7 +139,7 @@ pub struct ExtensionOptions { /// Requires the `broadcast_channel` feature to be enabled #[cfg(feature = "broadcast_channel")] #[cfg_attr(docsrs, doc(cfg(feature = "broadcast_channel")))] - pub broadcast_channel: deno_broadcast_channel::InMemoryBroadcastChannel, + pub broadcast_channel: deno_web::InMemoryBroadcastChannel, /// Key-value store for the `deno_kv` extension /// @@ -180,7 +180,7 @@ impl Default for ExtensionOptions { filesystem: std::sync::Arc::new(deno_fs::RealFs), #[cfg(feature = "broadcast_channel")] - broadcast_channel: deno_broadcast_channel::InMemoryBroadcastChannel::default(), + broadcast_channel: deno_web::InMemoryBroadcastChannel::default(), #[cfg(feature = "kv")] kv_store: kv::KvStore::default(), @@ -202,14 +202,18 @@ pub(crate) fn all_extensions( #[cfg(feature = "webidl")] extensions.extend(webidl::extensions(is_snapshot)); - #[cfg(feature = "console")] - extensions.extend(console::extensions(is_snapshot)); + // web or web_stub must come before url and console since they depend on deno_web + #[cfg(feature = "web")] + extensions.extend(web::extensions(options.web.clone(), is_snapshot)); + + #[cfg(all(not(feature = "web"), feature = "web_stub"))] + extensions.extend(web_stub::extensions(is_snapshot)); #[cfg(feature = "url")] extensions.extend(url::extensions(is_snapshot)); - #[cfg(feature = "web")] - extensions.extend(web::extensions(options.web.clone(), is_snapshot)); + #[cfg(feature = "console")] + extensions.extend(console::extensions(is_snapshot)); #[cfg(feature = "broadcast_channel")] extensions.extend(broadcast_channel::extensions( @@ -220,9 +224,6 @@ pub(crate) fn all_extensions( #[cfg(feature = "cache")] extensions.extend(cache::extensions(options.cache.clone(), is_snapshot)); - #[cfg(all(not(feature = "web"), feature = "web_stub"))] - extensions.extend(web_stub::extensions(is_snapshot)); - #[cfg(feature = "crypto")] extensions.extend(crypto::extensions(options.crypto_seed, is_snapshot)); @@ -236,7 +237,7 @@ pub(crate) fn all_extensions( )); #[cfg(feature = "websocket")] - extensions.extend(websocket::extensions(options.web.clone(), is_snapshot)); + extensions.extend(websocket::extensions(is_snapshot)); #[cfg(feature = "fs")] extensions.extend(fs::extensions(options.filesystem.clone(), is_snapshot)); diff --git a/src/ext/napi/mod.rs b/src/ext/napi/mod.rs index 56875c2..f0e48f1 100644 --- a/src/ext/napi/mod.rs +++ b/src/ext/napi/mod.rs @@ -1,8 +1,6 @@ -use std::borrow::Cow; - use deno_core::{extension, Extension}; -use super::{web::PermissionsContainer, ExtensionTrait}; +use super::ExtensionTrait; extension!( init_napi, @@ -10,14 +8,16 @@ extension!( esm_entry_point = "ext:init_napi/init_napi.js", esm = [ dir "src/ext/napi", "init_napi.js" ], ); + impl ExtensionTrait<()> for init_napi { fn init((): ()) -> Extension { init_napi::init() } } + impl ExtensionTrait<()> for deno_napi::deno_napi { fn init((): ()) -> Extension { - deno_napi::deno_napi::init::(None) + deno_napi::deno_napi::init(None) } } @@ -27,13 +27,3 @@ pub fn extensions(is_snapshot: bool) -> Vec { init_napi::build((), is_snapshot), ] } - -impl deno_napi::NapiPermissions for PermissionsContainer { - fn check<'a>( - &mut self, - path: Cow<'a, std::path::Path>, - ) -> Result, deno_permissions::PermissionCheckError> { - let p = self.0.check_read(path, None)?; - Ok(p) - } -} diff --git a/src/ext/node/mod.rs b/src/ext/node/mod.rs index 76866af..e9b2b9f 100644 --- a/src/ext/node/mod.rs +++ b/src/ext/node/mod.rs @@ -1,98 +1,39 @@ -use std::{borrow::Cow, path::Path, sync::Arc}; - -use deno_core::{extension, Extension}; -use deno_node::NodePermissions; -use deno_permissions::{CheckedPath, PermissionCheckError, PermissionDeniedError}; -use deno_resolver::npm::DenoInNpmPackageChecker; -use resolvers::{RustyNpmPackageFolderResolver, RustyResolver}; -use sys_traits::impls::RealSys; - -use super::{ - web::{PermissionsContainer, SystemsPermissionKind}, - ExtensionTrait, -}; - -mod cjs_translator; -pub mod resolvers; -pub use cjs_translator::NodeCodeTranslator; - -extension!( - init_node, - deps = [rustyscript], - esm_entry_point = "ext:init_node/init_node.js", - esm = [ dir "src/ext/node", "init_node.js" ], -); -impl ExtensionTrait<()> for init_node { - fn init((): ()) -> Extension { - init_node::init() - } -} -impl ExtensionTrait> for deno_node::deno_node { - fn init(resolver: Arc) -> Extension { - deno_node::deno_node::init::< - PermissionsContainer, - DenoInNpmPackageChecker, - RustyNpmPackageFolderResolver, - RealSys, - >(Some(resolver.init_services()), resolver.filesystem()) - } -} - -pub fn extensions(resolver: Arc, is_snapshot: bool) -> Vec { - vec![ - deno_node::deno_node::build(resolver, is_snapshot), - init_node::build((), is_snapshot), - ] -} - -impl NodePermissions for PermissionsContainer { - fn check_net( - &mut self, - host: (&str, Option), - api_name: &str, - ) -> Result<(), PermissionCheckError> { - self.0.check_host(host.0, host.1, api_name)?; - Ok(()) - } - - fn check_open<'a>( - &mut self, - path: Cow<'a, Path>, - open_access: deno_permissions::OpenAccessKind, - api_name: Option<&str>, - ) -> Result, PermissionCheckError> { - let read = open_access.is_read(); - let write = open_access.is_write(); - - let p = self - .0 - .check_open(true, read, write, path, api_name.unwrap_or_default()) - .ok_or(PermissionCheckError::PermissionDenied( - PermissionDeniedError { - access: api_name.unwrap_or_default().to_string(), - name: "open", - }, - ))?; - - Ok(CheckedPath::unsafe_new(p)) - } - - fn check_net_url( - &mut self, - url: &reqwest::Url, - api_name: &str, - ) -> std::result::Result<(), PermissionCheckError> { - self.0.check_url(url, api_name)?; - Ok(()) - } - - fn query_read_all(&mut self) -> bool { - self.0.check_read_all(None).is_ok() - } - - fn check_sys(&mut self, kind: &str, api_name: &str) -> Result<(), PermissionCheckError> { - let kind = SystemsPermissionKind::new(kind); - self.0.check_sys(kind, api_name)?; - Ok(()) - } -} +use std::sync::Arc; + +use deno_core::{extension, Extension}; +use deno_resolver::npm::DenoInNpmPackageChecker; +use resolvers::{RustyNpmPackageFolderResolver, RustyResolver}; +use sys_traits::impls::RealSys; + +use super::ExtensionTrait; + +mod cjs_translator; +pub mod resolvers; +pub use cjs_translator::NodeCodeTranslator; + +extension!( + init_node, + deps = [rustyscript], + esm_entry_point = "ext:init_node/init_node.js", + esm = [ dir "src/ext/node", "init_node.js" ], +); +impl ExtensionTrait<()> for init_node { + fn init((): ()) -> Extension { + init_node::init() + } +} +impl ExtensionTrait> for deno_node::deno_node { + fn init(resolver: Arc) -> Extension { + deno_node::deno_node::init::( + Some(resolver.init_services()), + resolver.filesystem(), + ) + } +} + +pub fn extensions(resolver: Arc, is_snapshot: bool) -> Vec { + vec![ + deno_node::deno_node::build(resolver, is_snapshot), + init_node::build((), is_snapshot), + ] +} diff --git a/src/ext/node/resolvers.rs b/src/ext/node/resolvers.rs index a1ddc14..31a010b 100644 --- a/src/ext/node/resolvers.rs +++ b/src/ext/node/resolvers.rs @@ -11,8 +11,8 @@ use deno_core::FastString; use deno_error::JsErrorBox; use deno_fs::{FileSystem, RealFs}; use deno_node::{NodeExtInitServices, NodeRequireLoader, NodeResolver}; -use deno_package_json::{PackageJsonCache, PackageJsonRc}; -use deno_permissions::{CheckedPath, OpenAccessKind}; +use deno_package_json::{PackageJsonCache, PackageJsonCacheResult, PackageJsonRc}; +use deno_permissions::{CheckedPath, OpenAccessKind, PermissionsContainer}; use deno_process::NpmProcessStateProvider; use deno_resolver::npm::{ ByonmInNpmPackageChecker, ByonmNpmResolver, ByonmNpmResolverCreateOptions, @@ -23,7 +23,7 @@ use node_resolver::{ analyze::{CjsModuleExportAnalyzer, NodeCodeTranslatorMode}, cache::NodeResolutionSys, errors::{ - ClosestPkgJsonError, PackageFolderResolveError, PackageFolderResolveErrorKind, + PackageFolderResolveError, PackageFolderResolveErrorKind, PackageJsonLoadError, PackageNotFoundError, }, DenoIsBuiltInNodeModuleChecker, InNpmPackageChecker, NodeConditionOptions, NodeResolutionCache, @@ -163,7 +163,7 @@ impl RustyResolver { fn check_based_on_pkg_json( &self, specifier: &ModuleSpecifier, - ) -> Result { + ) -> Result { let pjson = self.folder_resolver.pjson_resolver(); // Try to get a path from the URL @@ -203,6 +203,8 @@ impl RustyResolver { match media_type { MediaType::Wasm | MediaType::Json + | MediaType::Jsonc + | MediaType::Json5 | MediaType::Mts | MediaType::Mjs | MediaType::Html @@ -380,6 +382,17 @@ impl NpmPackageFolderResolver for RustyNpmPackageFolderResolver { .resolve_package_folder_from_package(specifier, referrer), } } + + fn resolve_types_package_folder( + &self, + _package_name: &str, + _version: Option<&deno_semver::Version>, + _referrer: Option<&UrlOrPathRef>, + ) -> Option { + // For BYONM (Bring Your Own Node Modules), types are resolved from node_modules + // This is not applicable in the current implementation + None + } } /// @@ -392,11 +405,20 @@ impl RustyPackageJsonCache { } } impl PackageJsonCache for RustyPackageJsonCache { - fn get(&self, path: &Path) -> Option { - self.0.read().ok().and_then(|i| i.get(path)) + fn get(&self, path: &Path) -> PackageJsonCacheResult { + match self.0.read().ok() { + Some(cache) => { + if cache.cache.contains_key(path) { + PackageJsonCacheResult::Hit(cache.cache.get(path).cloned().flatten()) + } else { + PackageJsonCacheResult::NotCached + } + } + None => PackageJsonCacheResult::NotCached, + } } - fn set(&self, path: PathBuf, package_json: PackageJsonRc) { + fn set(&self, path: PathBuf, package_json: Option) { if let Ok(mut i) = self.0.write() { i.set(path, package_json); } @@ -404,13 +426,14 @@ impl PackageJsonCache for RustyPackageJsonCache { } #[derive(Debug, Default, Clone)] pub struct RustyPackageJsonCacheInner { - cache: HashMap, + cache: HashMap>, } impl RustyPackageJsonCacheInner { + #[allow(dead_code)] fn get(&self, path: &Path) -> Option { - self.cache.get(path).cloned() + self.cache.get(path).cloned().flatten() } - fn set(&mut self, path: PathBuf, package_json: PackageJsonRc) { + fn set(&mut self, path: PathBuf, package_json: Option) { self.cache.insert(path, package_json); } } @@ -531,7 +554,7 @@ impl NodeRequireLoader for RequireLoader { fn ensure_read_permission<'a>( &self, - permissions: &mut dyn deno_node::NodePermissions, + permissions: &mut PermissionsContainer, path: Cow<'a, Path>, ) -> Result, JsErrorBox> { let is_in_node_modules = path @@ -547,7 +570,7 @@ impl NodeRequireLoader for RequireLoader { } } - fn is_maybe_cjs(&self, specifier: &reqwest::Url) -> Result { + fn is_maybe_cjs(&self, specifier: &reqwest::Url) -> Result { if specifier.scheme() != "file" { return Ok(false); } diff --git a/src/ext/runtime/init_runtime.js b/src/ext/runtime/init_runtime.js index 588f7a9..d484dfe 100644 --- a/src/ext/runtime/init_runtime.js +++ b/src/ext/runtime/init_runtime.js @@ -77,7 +77,7 @@ applyToDeno({ core: readOnly(core), }); -import * as _console from 'ext:deno_console/01_console.js'; +import * as _console from 'ext:deno_web/01_console.js'; _console.setNoColorFns( () => globalThis.Deno.core.ops.op_bootstrap_no_color(), () => globalThis.Deno.core.ops.op_bootstrap_no_color(), diff --git a/src/ext/runtime/mod.rs b/src/ext/runtime/mod.rs index 43ef723..b2b790d 100644 --- a/src/ext/runtime/mod.rs +++ b/src/ext/runtime/mod.rs @@ -1,265 +1,277 @@ -use std::{collections::HashSet, rc::Rc, sync::Arc}; - -use deno_core::{ - extension, - v8::{BackingStore, SharedRef}, - CrossIsolateStore, Extension, ExtensionFileSource, -}; -use deno_runtime::deno_permissions::Permissions; -use deno_runtime::permissions::RuntimePermissionDescriptorParser; -use deno_telemetry::OtelConfig; -use sys_traits::impls::RealSys; - -use super::{ - node::resolvers::RustyResolver, web::PermissionsContainer, ExtensionOptions, ExtensionTrait, -}; -use crate::module_loader::{LoaderOptions, RustyLoader}; - -fn build_permissions( - permissions_container: &PermissionsContainer, -) -> ::deno_permissions::PermissionsContainer { - let parser = Arc::new(RuntimePermissionDescriptorParser::::new(RealSys)); - ::deno_permissions::PermissionsContainer::new(parser, Permissions::allow_all()) -} - -// Some of the polyfills reference the denoland/deno runtime directly -// So we need to include a subset of the real thing -// -// However that extension lists nearly all others as dependencies so -// It will always be the last initialized extension -extension!( - init_runtime, - esm_entry_point = "ext:init_runtime/init_runtime.js", - esm = [ dir "src/ext/runtime", "init_runtime.js" ], - state = |state| { - let options = BootstrapOptions { - args: vec![ - "--colors".to_string(), - ], - ..BootstrapOptions::default() - }; - state.put(options); - - let container = state.borrow::(); - let permissions = build_permissions(container); - state.put(permissions); - }, - customizer = |e: &mut Extension| { - e.esm_files.to_mut().push( - ExtensionFileSource::new("ext:deno_features/flags.js", deno_features::JS_SOURCE) - ); - } -); -impl ExtensionTrait<()> for init_runtime { - fn init((): ()) -> Extension { - init_runtime::init() - } -} - -impl ExtensionTrait<()> for deno_runtime::runtime { - fn init((): ()) -> Extension { - let mut e = deno_runtime::runtime::init(); - e.esm_entry_point = None; - e - } -} - -use deno_runtime::fmt_errors::format_js_error; -use deno_runtime::ops::permissions::deno_permissions; -impl ExtensionTrait<()> for deno_permissions { - fn init((): ()) -> Extension { - deno_permissions::init() - } -} - -use deno_runtime::ops::worker_host::{deno_worker_host, CreateWebWorkerCb}; -impl - ExtensionTrait<( - &ExtensionOptions, - Option>>, - )> for deno_worker_host -{ - fn init( - options: ( - &ExtensionOptions, - Option>>, - ), - ) -> Extension { - let options = WebWorkerCallbackOptions::new(options.0, options.1); - let callback = create_web_worker_callback(options); - deno_worker_host::init(callback, None) - } -} - -use deno_runtime::ops::web_worker::deno_web_worker; -impl ExtensionTrait<()> for deno_web_worker { - fn init((): ()) -> Extension { - deno_web_worker::init() - } -} - -use deno_process::deno_process; -impl ExtensionTrait> for deno_process { - fn init(resolver: Arc) -> Extension { - deno_process::init(Some(resolver)) - } -} - -use deno_runtime::deno_os::{deno_os, ExitCode}; -impl ExtensionTrait<()> for deno_os { - fn init((): ()) -> Extension { - deno_os::init(Some(ExitCode::default())) - } -} - -use deno_runtime::ops::bootstrap::deno_bootstrap; -impl ExtensionTrait<()> for deno_bootstrap { - fn init((): ()) -> Extension { - deno_bootstrap::init(None, false) - } -} - -use deno_runtime::ops::fs_events::deno_fs_events; -impl ExtensionTrait<()> for deno_fs_events { - fn init((): ()) -> Extension { - deno_fs_events::init() - } -} - -pub fn extensions( - options: &ExtensionOptions, - shared_array_buffer_store: Option>>, - is_snapshot: bool, -) -> Vec { - vec![ - deno_fs_events::build((), is_snapshot), - deno_bootstrap::build((), is_snapshot), - deno_os::build((), is_snapshot), - deno_process::build(options.node_resolver.clone(), is_snapshot), - deno_web_worker::build((), is_snapshot), - deno_worker_host::build((options, shared_array_buffer_store), is_snapshot), - deno_permissions::build((), is_snapshot), - // - deno_runtime::runtime::build((), is_snapshot), - init_runtime::build((), is_snapshot), - ] -} - -use deno_runtime::web_worker::{WebWorker, WebWorkerOptions, WebWorkerServiceOptions}; -use deno_runtime::{colors, BootstrapOptions, WorkerExecutionMode, WorkerLogLevel}; -#[derive(Clone)] -pub struct WebWorkerCallbackOptions { - shared_array_buffer_store: Option>>, - node_resolver: Arc, - root_cert_store_provider: Option>, - broadcast_channel: deno_broadcast_channel::InMemoryBroadcastChannel, - unsafely_ignore_certificate_errors: Option>, - seed: Option, - stdio: deno_io::Stdio, - blob_store: Arc, -} -impl WebWorkerCallbackOptions { - pub fn new( - options: &ExtensionOptions, - shared_array_buffer_store: Option>>, - ) -> Self { - Self { - shared_array_buffer_store, - node_resolver: options.node_resolver.clone(), - root_cert_store_provider: options.web.root_cert_store_provider.clone(), - broadcast_channel: options.broadcast_channel.clone(), - unsafely_ignore_certificate_errors: options - .web - .unsafely_ignore_certificate_errors - .clone(), - seed: options.crypto_seed, - stdio: options.io_pipes.clone().unwrap_or_default(), - blob_store: options.web.blob_store.clone(), - } - } -} - -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -fn create_web_worker_callback(options: WebWorkerCallbackOptions) -> Arc { - Arc::new(move |args| { - let node_resolver = options.node_resolver.clone(); - let module_loader = Rc::new(RustyLoader::new(LoaderOptions { - cache_provider: None, - import_provider: None, - schema_whlist: HashSet::default(), - node_resolver: node_resolver.clone(), - ..Default::default() - })); - - let create_web_worker_cb = create_web_worker_callback(options.clone()); - - let mut feature_checker = deno_features::FeatureChecker::default(); - feature_checker.set_exit_cb(Box::new(|_, _| {})); - - let services = WebWorkerServiceOptions { - root_cert_store_provider: options.root_cert_store_provider.clone(), - module_loader, - fs: node_resolver.filesystem(), - node_services: Some(node_resolver.init_services()), - blob_store: options.blob_store.clone(), - broadcast_channel: options.broadcast_channel.clone(), - shared_array_buffer_store: options.shared_array_buffer_store.clone(), - compiled_wasm_module_store: None, - maybe_inspector_server: None, - feature_checker: feature_checker.into(), - npm_process_state_provider: Some(node_resolver.clone()), - permissions: args.permissions, - deno_rt_native_addon_loader: None, - }; - - let options = WebWorkerOptions { - enable_raw_imports: true, - name: args.name, - main_module: args.main_module.clone(), - worker_id: args.worker_id, - bootstrap: BootstrapOptions { - deno_version: env!("CARGO_PKG_VERSION").to_string(), - args: vec![], - cpu_count: std::thread::available_parallelism() - .map(std::num::NonZero::get) - .unwrap_or(1), - log_level: WorkerLogLevel::default(), - enable_op_summary_metrics: false, - enable_testing_features: false, - locale: deno_core::v8::icu::get_language_tag(), - location: Some(args.main_module), - color_level: colors::get_color_level(), - unstable_features: vec![], - user_agent: concat!("rustyscript_", env!("CARGO_PKG_VERSION")).to_string(), - inspect: false, - has_node_modules_dir: node_resolver.has_node_modules_dir(), - argv0: None, - node_debug: None, - node_ipc_fd: None, - mode: WorkerExecutionMode::Worker, - serve_port: None, - serve_host: None, - otel_config: OtelConfig::default(), - close_on_idle: false, - no_legacy_abort: false, - is_standalone: false, - auto_serve: false, - }, - extensions: vec![], - startup_snapshot: None, - unsafely_ignore_certificate_errors: options.unsafely_ignore_certificate_errors.clone(), - seed: options.seed, - create_web_worker_cb, - format_js_error_fn: Some(Arc::new(format_js_error)), - worker_type: args.worker_type, - stdio: options.stdio.clone(), - cache_storage_dir: None, - strace_ops: None, - close_on_idle: false, - maybe_worker_metadata: None, - create_params: None, - enable_stack_trace_arg_in_ops: false, - }; - WebWorker::bootstrap_from_options(services, options) - }) -} +use std::{collections::HashSet, rc::Rc, sync::Arc}; + +use deno_bundle_runtime::BundleProvider; +use deno_core::{ + extension, + v8::{BackingStore, SharedRef}, + CrossIsolateStore, Extension, ExtensionFileSource, +}; +use deno_runtime::deno_permissions::Permissions; +use deno_runtime::permissions::RuntimePermissionDescriptorParser; +use deno_telemetry::OtelConfig; +use sys_traits::impls::RealSys; + +use super::{ + node::resolvers::RustyResolver, web::PermissionsContainer, ExtensionOptions, ExtensionTrait, +}; +use crate::module_loader::{LoaderOptions, RustyLoader}; + +fn build_permissions( + permissions_container: &PermissionsContainer, +) -> ::deno_permissions::PermissionsContainer { + let parser = Arc::new(RuntimePermissionDescriptorParser::::new(RealSys)); + ::deno_permissions::PermissionsContainer::new(parser, Permissions::allow_all()) +} + +// Some of the polyfills reference the denoland/deno runtime directly +// So we need to include a subset of the real thing +// +// However that extension lists nearly all others as dependencies so +// It will always be the last initialized extension +extension!( + init_runtime, + esm_entry_point = "ext:init_runtime/init_runtime.js", + esm = [ dir "src/ext/runtime", "init_runtime.js" ], + state = |state| { + let options = BootstrapOptions { + args: vec![ + "--colors".to_string(), + ], + ..BootstrapOptions::default() + }; + state.put(options); + + let container = state.borrow::(); + let permissions = build_permissions(container); + state.put(permissions); + }, + customizer = |e: &mut Extension| { + e.esm_files.to_mut().push( + ExtensionFileSource::new("ext:deno_features/flags.js", deno_features::JS_SOURCE) + ); + } +); +impl ExtensionTrait<()> for init_runtime { + fn init((): ()) -> Extension { + init_runtime::init() + } +} + +impl ExtensionTrait<()> for deno_runtime::runtime { + fn init((): ()) -> Extension { + let mut e = deno_runtime::runtime::init(); + e.esm_entry_point = None; + e + } +} + +impl ExtensionTrait>> for deno_bundle_runtime::deno_bundle_runtime { + fn init(bundle_provider: Option>) -> Extension { + deno_bundle_runtime::deno_bundle_runtime::init(bundle_provider) + } +} + +use deno_runtime::fmt_errors::format_js_error; +use deno_runtime::ops::permissions::deno_permissions; +impl ExtensionTrait<()> for deno_permissions { + fn init((): ()) -> Extension { + deno_permissions::init() + } +} + +use deno_runtime::ops::worker_host::{deno_worker_host, CreateWebWorkerCb}; +impl + ExtensionTrait<( + &ExtensionOptions, + Option>>, + )> for deno_worker_host +{ + fn init( + options: ( + &ExtensionOptions, + Option>>, + ), + ) -> Extension { + let options = WebWorkerCallbackOptions::new(options.0, options.1); + let callback = create_web_worker_callback(options); + deno_worker_host::init(callback, None) + } +} + +use deno_runtime::ops::web_worker::deno_web_worker; +impl ExtensionTrait<()> for deno_web_worker { + fn init((): ()) -> Extension { + deno_web_worker::init() + } +} + +use deno_process::deno_process; +impl ExtensionTrait> for deno_process { + fn init(resolver: Arc) -> Extension { + deno_process::init(Some(resolver)) + } +} + +use deno_runtime::deno_os::{deno_os, ExitCode}; +impl ExtensionTrait<()> for deno_os { + fn init((): ()) -> Extension { + deno_os::init(Some(ExitCode::default())) + } +} + +use deno_runtime::ops::bootstrap::deno_bootstrap; +impl ExtensionTrait<()> for deno_bootstrap { + fn init((): ()) -> Extension { + deno_bootstrap::init(None, false) + } +} + +use deno_runtime::ops::fs_events::deno_fs_events; +impl ExtensionTrait<()> for deno_fs_events { + fn init((): ()) -> Extension { + deno_fs_events::init() + } +} + +pub fn extensions( + options: &ExtensionOptions, + shared_array_buffer_store: Option>>, + is_snapshot: bool, +) -> Vec { + vec![ + deno_bundle_runtime::deno_bundle_runtime::build(None, is_snapshot), + deno_fs_events::build((), is_snapshot), + deno_bootstrap::build((), is_snapshot), + deno_os::build((), is_snapshot), + deno_process::build(options.node_resolver.clone(), is_snapshot), + deno_web_worker::build((), is_snapshot), + deno_worker_host::build((options, shared_array_buffer_store), is_snapshot), + deno_permissions::build((), is_snapshot), + // + deno_runtime::runtime::build((), is_snapshot), + init_runtime::build((), is_snapshot), + ] +} + +use deno_runtime::inspector_server::MainInspectorSessionChannel; +use deno_runtime::web_worker::{WebWorker, WebWorkerOptions, WebWorkerServiceOptions}; +use deno_runtime::{colors, BootstrapOptions, WorkerExecutionMode, WorkerLogLevel}; +#[derive(Clone)] +pub struct WebWorkerCallbackOptions { + shared_array_buffer_store: Option>>, + node_resolver: Arc, + root_cert_store_provider: Option>, + broadcast_channel: deno_web::InMemoryBroadcastChannel, + unsafely_ignore_certificate_errors: Option>, + seed: Option, + stdio: deno_io::Stdio, + blob_store: Arc, +} +impl WebWorkerCallbackOptions { + pub fn new( + options: &ExtensionOptions, + shared_array_buffer_store: Option>>, + ) -> Self { + Self { + shared_array_buffer_store, + node_resolver: options.node_resolver.clone(), + root_cert_store_provider: options.web.root_cert_store_provider.clone(), + broadcast_channel: options.broadcast_channel.clone(), + unsafely_ignore_certificate_errors: options + .web + .unsafely_ignore_certificate_errors + .clone(), + seed: options.crypto_seed, + stdio: options.io_pipes.clone().unwrap_or_default(), + blob_store: options.web.blob_store.clone(), + } + } +} + +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +fn create_web_worker_callback(options: WebWorkerCallbackOptions) -> Arc { + Arc::new(move |args| { + let node_resolver = options.node_resolver.clone(); + let module_loader = Rc::new(RustyLoader::new(LoaderOptions { + cache_provider: None, + import_provider: None, + schema_whlist: HashSet::default(), + node_resolver: node_resolver.clone(), + ..Default::default() + })); + + let create_web_worker_cb = create_web_worker_callback(options.clone()); + + let mut feature_checker = deno_features::FeatureChecker::default(); + feature_checker.set_exit_cb(Box::new(|_, _| {})); + + let services = WebWorkerServiceOptions { + root_cert_store_provider: options.root_cert_store_provider.clone(), + module_loader, + fs: node_resolver.filesystem(), + node_services: Some(node_resolver.init_services()), + blob_store: options.blob_store.clone(), + broadcast_channel: options.broadcast_channel.clone(), + shared_array_buffer_store: options.shared_array_buffer_store.clone(), + compiled_wasm_module_store: None, + maybe_inspector_server: None, + feature_checker: feature_checker.into(), + npm_process_state_provider: Some(node_resolver.clone()), + permissions: args.permissions, + deno_rt_native_addon_loader: None, + bundle_provider: None, + main_inspector_session_tx: MainInspectorSessionChannel::default(), + }; + + let options = WebWorkerOptions { + enable_raw_imports: true, + name: args.name, + main_module: args.main_module.clone(), + worker_id: args.worker_id, + bootstrap: BootstrapOptions { + deno_version: env!("CARGO_PKG_VERSION").to_string(), + args: vec![], + cpu_count: std::thread::available_parallelism() + .map(std::num::NonZero::get) + .unwrap_or(1), + log_level: WorkerLogLevel::default(), + enable_op_summary_metrics: false, + enable_testing_features: false, + locale: deno_core::v8::icu::get_language_tag(), + location: Some(args.main_module), + color_level: colors::get_color_level(), + unstable_features: vec![], + user_agent: concat!("rustyscript_", env!("CARGO_PKG_VERSION")).to_string(), + inspect: false, + has_node_modules_dir: node_resolver.has_node_modules_dir(), + argv0: None, + node_debug: None, + node_ipc_init: None, + mode: WorkerExecutionMode::Worker, + serve_port: None, + serve_host: None, + otel_config: OtelConfig::default(), + close_on_idle: false, + no_legacy_abort: false, + is_standalone: false, + auto_serve: false, + }, + extensions: vec![], + startup_snapshot: None, + unsafely_ignore_certificate_errors: options.unsafely_ignore_certificate_errors.clone(), + seed: options.seed, + create_web_worker_cb, + format_js_error_fn: Some(Arc::new(|e| format_js_error(e, None))), + worker_type: args.worker_type, + stdio: options.stdio.clone(), + cache_storage_dir: None, + trace_ops: None, + close_on_idle: false, + maybe_worker_metadata: None, + create_params: None, + enable_stack_trace_arg_in_ops: false, + maybe_coverage_dir: None, + }; + WebWorker::bootstrap_from_options(services, options) + }) +} diff --git a/src/ext/rustyscript/callbacks.rs b/src/ext/rustyscript/callbacks.rs index 048fb76..1ecaa93 100644 --- a/src/ext/rustyscript/callbacks.rs +++ b/src/ext/rustyscript/callbacks.rs @@ -1,170 +1,191 @@ -#![allow(dead_code)] - -use std::{future::Future, pin::Pin, rc::Rc}; - -use crate::Error; -use deno_core::{op2, serde_json, v8, OpState}; -use paste::paste; - -pub trait RsStoredCallback: 'static { - fn call( - &self, - args: deno_core::serde_json::Value, - ) -> Pin>>>; - - fn encode_args( - &self, - args: v8::Global, - scope: &mut v8::HandleScope<'_>, - ) -> Result; -} - -pub trait RsCallback: 'static { - /// A tuple of the arguments that the function takes - type Arguments: serde::ser::Serialize + serde::de::DeserializeOwned; - - /// The return type of the function - type Return: serde::ser::Serialize + 'static; - - /// Function body - async fn body(args: Self::Arguments) -> Result; - - /// Convert a series of `v8::Value` objects into a tuple of arguments - fn args_from_v8( - args: Vec>, - scope: &mut v8::HandleScope, - ) -> Result; - - fn slow_args_from_v8( - args: Vec>, - scope: &mut v8::HandleScope, - ) -> Result { - let args = Self::args_from_v8(args, scope)?; - deno_core::serde_json::to_value(args).map_err(Error::from) - } - - /// Convert a series of `v8::Value` objects into a tuple of arguments - fn decode_v8( - args: v8::Global, - scope: &mut v8::HandleScope, - ) -> Result { - let args = v8::Local::new(scope, args); - let args = if args.is_array() { - let args: v8::Local = v8::Local::new(scope, args).try_into()?; - let len = args.length() as usize; - let mut result = Vec::with_capacity(len); - for i in 0..len { - let index = v8::Integer::new( - scope, - i.try_into().map_err(|_| { - Error::Runtime(format!( - "Could not decode {len} arguments - use `big_json_args`" - )) - })?, - ); - let arg = args - .get(scope, index.into()) - .ok_or_else(|| Error::Runtime(format!("Invalid argument at index {i}")))?; - result.push(v8::Global::new(scope, arg)); - } - result - } else { - vec![v8::Global::new(scope, args)] - }; - - Self::args_from_v8(args, scope) - } - - /// Call the function - async fn call( - args: v8::Global, - scope: &mut v8::HandleScope<'_>, - ) -> Result { - let args = Self::decode_v8(args, scope)?; - Self::body(args).await - } -} - -macro_rules! codegen_function { - ($(#[doc = $doc:literal])* fn $name:ident ($($n:ident:$t:ty),+ $(,)?) -> $r:ty $body:block ) => { - paste! { - #[allow(non_camel_case_types)] - $(#[doc = $doc])* - struct [< rscallback_ $name >](); - impl RsCallback for [< rscallback_ $name >] { - type Arguments = ($($t,)+); - type Return = $r; - - fn args_from_v8( - args: Vec>, - scope: &mut v8::HandleScope, - ) -> Result { - let mut args = args.into_iter(); - $( - let next = args.next().ok_or($crate::Error::Runtime(format!("Missing argument {} for {}", stringify!($n), stringify!($name))))?; - let next = $crate::deno_core::v8::Local::new(scope, next); - let $n:$t = $crate::deno_core::serde_v8::from_v8(scope, next)?; - )+ - Ok(($($n,)+)) - } - - async fn body(($($n,)+): Self::Arguments) -> Result { - $body - } - } - impl RsStoredCallback for [< rscallback_ $name >] { - fn call(&self, args: $crate::deno_core::serde_json::Value) - -> std::pin::Pin>>> { - Box::pin(async move { - let args: ::Arguments = $crate::deno_core::serde_json::from_value(args).map_err($crate::Error::from)?; - - let v = Self::body(args).await?; - $crate::deno_core::serde_json::to_value(v).map_err($crate::Error::from) - }) - } - - fn encode_args(&self, args: $crate::deno_core::v8::Global<$crate::deno_core::v8::Value>, scope: &mut $crate::deno_core::v8::HandleScope<'_>) -> Result<$crate::deno_core::serde_json::Value, $crate::Error> { - let args = Self::decode_v8(args, scope)?; - Ok($crate::serde_json::to_value(args)?) - } - } - } - } -} - -macro_rules! rs_fn { - ($($(#[doc = $doc:literal])* fn $name:ident ($($n:ident:$t:ty),+ $(,)?) -> $r:ty $body:block )+) => { - $(codegen_function! { fn $name ($($n:$t),+) -> $r $body })+ - } -} - -rs_fn! { - /// test - fn add(a: i64, b: i64) -> i64 { - Ok(a + b) - } -} - -#[op2(async)] -#[serde] -pub async fn run_rscallback( - #[serde] args: T::Arguments, -) -> Result { - T::body(args).await -} - -type CallbackTable = std::collections::HashMap>>; - -#[op2(async)] -#[serde] -pub fn rscallback( - #[string] name: &str, - #[serde] args: deno_core::serde_json::Value, - state: &mut OpState, -) -> impl std::future::Future> { - let callback = state - .try_borrow::() - .and_then(|t| t.get(name).cloned()) - .ok_or_else(|| Error::ValueNotCallable(name.to_string())); - async move { callback?.call(args).await } -} +#![allow(dead_code)] + +use std::{future::Future, pin::Pin, rc::Rc}; + +use crate::Error; +use deno_core::{op2, serde_json, v8, OpState}; +use paste::paste; + +pub trait RsStoredCallback: 'static { + fn call( + &self, + args: deno_core::serde_json::Value, + ) -> Pin>>>; + + fn encode_args( + &self, + args: v8::Global, + scope: &mut v8::HandleScope<'_>, + ) -> Result; +} + +pub trait RsCallback: 'static { + /// A tuple of the arguments that the function takes + type Arguments: serde::ser::Serialize + serde::de::DeserializeOwned; + + /// The return type of the function + type Return: serde::ser::Serialize + 'static; + + /// Function body + async fn body(args: Self::Arguments) -> Result; + + /// Convert a series of `v8::Value` objects into a tuple of arguments + fn args_from_v8( + args: Vec>, + scope: &mut v8::HandleScope<'_>, + ) -> Result; + + fn slow_args_from_v8( + args: Vec>, + scope: &mut v8::HandleScope<'_>, + ) -> Result { + let args = Self::args_from_v8(args, scope)?; + deno_core::serde_json::to_value(args).map_err(Error::from) + } + + /// Convert a series of `v8::Value` objects into a tuple of arguments + fn decode_v8( + args: v8::Global, + scope: &mut v8::HandleScope<'_>, + ) -> Result { + // SAFETY: v8 API requires &PinnedRef but &mut HandleScope has the same layout + // We transmute to satisfy the type requirements while maintaining safety + let scope_ref = + unsafe { &*std::ptr::from_mut(scope).cast::>>() }; + // SAFETY: Object methods require HandleScope + let ctx_scope = unsafe { + &*std::ptr::from_mut(scope).cast::>>() + }; + // SAFETY: v8::Global::new requires &Isolate + let isolate = unsafe { &*std::ptr::from_mut(scope).cast::() }; + let args_local = v8::Local::new(scope_ref, args); + + let args = if args_local.is_array() { + let args: v8::Local = v8::Local::new(scope_ref, args_local).try_into()?; + let len = args.length() as usize; + let mut result = Vec::with_capacity(len); + for i in 0..len { + let index = v8::Integer::new( + scope_ref, + i.try_into().map_err(|_| { + Error::Runtime(format!( + "Could not decode {len} arguments - use `big_json_args`" + )) + })?, + ); + let arg = args + .get(ctx_scope, index.into()) + .ok_or_else(|| Error::Runtime(format!("Invalid argument at index {i}")))?; + result.push(v8::Global::new(isolate, arg)); + } + result + } else { + vec![v8::Global::new(isolate, args_local)] + }; + + Self::args_from_v8(args, scope) + } + + /// Call the function + async fn call( + args: v8::Global, + scope: &mut v8::HandleScope<'_>, + ) -> Result { + let args = Self::decode_v8(args, scope)?; + Self::body(args).await + } +} + +macro_rules! codegen_function { + ($(#[doc = $doc:literal])* fn $name:ident ($($n:ident:$t:ty),+ $(,)?) -> $r:ty $body:block ) => { + paste! { + #[allow(non_camel_case_types)] + $(#[doc = $doc])* + struct [< rscallback_ $name >](); + impl RsCallback for [< rscallback_ $name >] { + type Arguments = ($($t,)+); + type Return = $r; + + fn args_from_v8( + args: Vec>, + scope: &mut v8::HandleScope<'_>, + ) -> Result { + // SAFETY: v8 API requires PinnedRef types but HandleScope has the same layout + let scope_ref = unsafe { + &*std::ptr::from_mut(scope) + .cast::<$crate::deno_core::v8::PinnedRef<$crate::deno_core::v8::HandleScope<'_, ()>>>() + }; + let scope_mut = unsafe { + &mut *std::ptr::from_mut(scope).cast::<$crate::deno_core::v8::PinnedRef< + $crate::deno_core::v8::HandleScope<'_, $crate::deno_core::v8::Context>, + >>() + }; + let mut args = args.into_iter(); + $( + let next = args.next().ok_or($crate::Error::Runtime(format!("Missing argument {} for {}", stringify!($n), stringify!($name))))?; + let next = $crate::deno_core::v8::Local::new(scope_ref, next); + let $n:$t = $crate::deno_core::serde_v8::from_v8(scope_mut, next)?; + )+ + Ok(($($n,)+)) + } + + async fn body(($($n,)+): Self::Arguments) -> Result { + $body + } + } + impl RsStoredCallback for [< rscallback_ $name >] { + fn call(&self, args: $crate::deno_core::serde_json::Value) + -> std::pin::Pin>>> { + Box::pin(async move { + let args: ::Arguments = $crate::deno_core::serde_json::from_value(args).map_err($crate::Error::from)?; + + let v = Self::body(args).await?; + $crate::deno_core::serde_json::to_value(v).map_err($crate::Error::from) + }) + } + + fn encode_args(&self, args: $crate::deno_core::v8::Global<$crate::deno_core::v8::Value>, scope: &mut $crate::deno_core::v8::HandleScope<'_>) -> Result<$crate::deno_core::serde_json::Value, $crate::Error> { + let args = Self::decode_v8(args, scope)?; + Ok($crate::serde_json::to_value(args)?) + } + } + } + } +} + +macro_rules! rs_fn { + ($($(#[doc = $doc:literal])* fn $name:ident ($($n:ident:$t:ty),+ $(,)?) -> $r:ty $body:block )+) => { + $(codegen_function! { fn $name ($($n:$t),+) -> $r $body })+ + } +} + +rs_fn! { + /// test + fn add(a: i64, b: i64) -> i64 { + Ok(a + b) + } +} + +#[op2(async)] +#[serde] +pub async fn run_rscallback( + #[serde] args: T::Arguments, +) -> Result { + T::body(args).await +} + +type CallbackTable = std::collections::HashMap>>; + +#[op2(async)] +#[serde] +pub fn rscallback( + #[string] name: &str, + #[serde] args: deno_core::serde_json::Value, + state: &mut OpState, +) -> impl std::future::Future> { + let callback = state + .try_borrow::() + .and_then(|t| t.get(name).cloned()) + .ok_or_else(|| Error::ValueNotCallable(name.to_string())); + async move { callback?.call(args).await } +} diff --git a/src/ext/url/init_url.js b/src/ext/url/init_url.js index ae3a491..4f15ae9 100644 --- a/src/ext/url/init_url.js +++ b/src/ext/url/init_url.js @@ -1,5 +1,5 @@ -import * as url from 'ext:deno_url/00_url.js'; -import * as urlPattern from 'ext:deno_url/01_urlpattern.js'; +import * as url from 'ext:deno_web/00_url.js'; +import * as urlPattern from 'ext:deno_web/01_urlpattern.js'; import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; applyToGlobal({ diff --git a/src/ext/url/mod.rs b/src/ext/url/mod.rs index cb1e3f4..99b88eb 100644 --- a/src/ext/url/mod.rs +++ b/src/ext/url/mod.rs @@ -1,27 +1,21 @@ -use deno_core::{extension, Extension}; - -use super::ExtensionTrait; - -extension!( - init_url, - deps = [rustyscript], - esm_entry_point = "ext:init_url/init_url.js", - esm = [ dir "src/ext/url", "init_url.js" ], -); -impl ExtensionTrait<()> for init_url { - fn init((): ()) -> Extension { - init_url::init() - } -} -impl ExtensionTrait<()> for deno_url::deno_url { - fn init((): ()) -> Extension { - deno_url::deno_url::init() - } -} - -pub fn extensions(is_snapshot: bool) -> Vec { - vec![ - deno_url::deno_url::build((), is_snapshot), - init_url::build((), is_snapshot), - ] -} +use deno_core::{extension, Extension}; + +use super::ExtensionTrait; + +extension!( + init_url, + deps = [rustyscript, deno_web], + esm_entry_point = "ext:init_url/init_url.js", + esm = [ dir "src/ext/url", "init_url.js" ], +); +impl ExtensionTrait<()> for init_url { + fn init((): ()) -> Extension { + init_url::init() + } +} + +// deno_url now uses extension macro and doesn't export init_ops_and_esm separately +// It's integrated into other extensions like deno_web +pub fn extensions(is_snapshot: bool) -> Vec { + vec![init_url::build((), is_snapshot)] +} diff --git a/src/ext/web/init_errors.js b/src/ext/web/init_errors.js index 2731081..3620892 100644 --- a/src/ext/web/init_errors.js +++ b/src/ext/web/init_errors.js @@ -19,7 +19,7 @@ import { getStderrNoColor, inspectArgs, quoteString, -} from "ext:deno_console/01_console.js"; +} from "ext:deno_web/01_console.js"; class NotFound extends Error { constructor(msg) { @@ -347,4 +347,4 @@ globalThis_ = globalThis; primordials.ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype); event.saveGlobalThisReference(globalThis); -event.setEventTargetData(globalThis); \ No newline at end of file +event.setEventTargetData(globalThis); diff --git a/src/ext/web/init_web.js b/src/ext/web/init_web.js index db5cce3..17c7164 100644 --- a/src/ext/web/init_web.js +++ b/src/ext/web/init_web.js @@ -16,6 +16,7 @@ import * as messagePort from 'ext:deno_web/13_message_port.js'; import * as compression from 'ext:deno_web/14_compression.js'; import * as performance from 'ext:deno_web/15_performance.js'; import * as imageData from 'ext:deno_web/16_image_data.js'; +import * as broadcastChannel from 'ext:deno_web/01_broadcast_channel.js'; import * as errors from 'ext:init_web/init_errors.js'; diff --git a/src/ext/web/mod.rs b/src/ext/web/mod.rs index 7647667..a045ae0 100644 --- a/src/ext/web/mod.rs +++ b/src/ext/web/mod.rs @@ -1,6 +1,8 @@ -use std::sync::Arc; - -use deno_core::{extension, Extension}; +use std::sync::Arc; + +use deno_core::{extension, Extension}; +use deno_permissions::RuntimePermissionDescriptorParser; +use sys_traits::impls::RealSys; use super::ExtensionTrait; @@ -14,11 +16,12 @@ pub use permissions::{ PermissionDeniedError, SystemsPermissionKind, WebPermissions, }; -/// Stub for a node op deno_net expects to find -/// We return None to show no cert available -#[deno_core::op2] -#[serde] -pub fn op_tls_peer_certificate( +/// Stub for a node op `deno_net` expects to find +/// We return None to show no cert available +#[allow(dead_code)] +#[deno_core::op2] +#[serde] +pub fn op_tls_peer_certificate( #[smi] _rid: u32, _detailed: bool, ) -> Option { @@ -27,7 +30,7 @@ pub fn op_tls_peer_certificate( extension!( init_fetch, - deps = [rustyscript], + deps = [rustyscript, deno_fetch, init_web], esm_entry_point = "ext:init_fetch/init_fetch.js", esm = [ dir "src/ext/web", "init_fetch.js" ], ); @@ -50,14 +53,14 @@ impl ExtensionTrait for deno_fetch::deno_fetch { resolver: options.resolver.clone(), }; - deno_fetch::deno_fetch::init::(options) + deno_fetch::deno_fetch::init(options) } } #[cfg(not(feature = "node_experimental"))] extension!( init_net, - deps = [rustyscript], + deps = [rustyscript, deno_net, init_web], ops = [op_tls_peer_certificate], esm_entry_point = "ext:init_net/init_net.js", esm = [ dir "src/ext/web", "init_net.js" ], @@ -66,7 +69,7 @@ extension!( #[cfg(feature = "node_experimental")] extension!( init_net, - deps = [rustyscript], + deps = [rustyscript, deno_net, init_web], esm_entry_point = "ext:init_net/init_net.js", esm = [ dir "src/ext/web", "init_net.js" ], ); @@ -80,7 +83,7 @@ impl ExtensionTrait for init_net { } impl ExtensionTrait for deno_net::deno_net { fn init(options: WebOptions) -> Extension { - deno_net::deno_net::init::( + deno_net::deno_net::init( options.root_cert_store_provider.clone(), options.unsafely_ignore_certificate_errors.clone(), ) @@ -89,7 +92,7 @@ impl ExtensionTrait for deno_net::deno_net { extension!( init_telemetry, - deps = [rustyscript], + deps = [rustyscript, deno_telemetry, init_web], esm_entry_point = "ext:init_telemetry/init_telemetry.js", esm = [ dir "src/ext/web", "init_telemetry.js" ], ); @@ -105,16 +108,23 @@ impl ExtensionTrait<()> for deno_telemetry::deno_telemetry { } } -extension!( - init_web, - deps = [rustyscript], - esm_entry_point = "ext:init_web/init_web.js", - esm = [ dir "src/ext/web", "init_web.js", "init_errors.js" ], - options = { - permissions: Arc - }, - state = |state, config| state.put(PermissionsContainer(config.permissions)), -); +extension!( + init_web, + deps = [rustyscript, deno_webidl, deno_web], + esm_entry_point = "ext:init_web/init_web.js", + esm = [ dir "src/ext/web", "init_web.js", "init_errors.js" ], + options = { + permissions: Arc + }, + state = |state, config| { + state.put(PermissionsContainer(config.permissions)); + if !state.has::() { + let parser = Arc::new(RuntimePermissionDescriptorParser::new(RealSys)); + let permissions = deno_permissions::PermissionsContainer::allow_all(parser); + state.put(permissions); + } + }, +); impl ExtensionTrait for init_web { fn init(options: WebOptions) -> Extension { init_web::init(options.permissions) @@ -123,7 +133,11 @@ impl ExtensionTrait for init_web { impl ExtensionTrait for deno_web::deno_web { fn init(options: WebOptions) -> Extension { - deno_web::deno_web::init::(options.blob_store, options.base_url) + deno_web::deno_web::init( + options.blob_store, + options.base_url, + options.broadcast_channel, + ) } } diff --git a/src/ext/web/options.rs b/src/ext/web/options.rs index bdf0abc..df8bfed 100644 --- a/src/ext/web/options.rs +++ b/src/ext/web/options.rs @@ -42,6 +42,9 @@ pub struct WebOptions { /// Blob store for the web related extensions pub blob_store: Arc, + /// Broadcast channel for cross-context messaging + pub broadcast_channel: deno_web::InMemoryBroadcastChannel, + ///A callback to customize HTTP client configuration. /// /// For more info on what can be configured, see [`hyper_util::client::legacy::Builder`] @@ -67,6 +70,7 @@ impl Default for WebOptions { file_fetch_handler: std::rc::Rc::new(deno_fetch::DefaultFileFetchHandler), permissions: Arc::new(DefaultWebPermissions), blob_store: Arc::new(deno_web::BlobStore::default()), + broadcast_channel: deno_web::InMemoryBroadcastChannel::default(), client_builder_hook: None, resolver: Resolver::default(), telemetry_config: deno_telemetry::OtelConfig::default(), diff --git a/src/ext/web/permissions.rs b/src/ext/web/permissions.rs index eebf53f..ccf1ed8 100644 --- a/src/ext/web/permissions.rs +++ b/src/ext/web/permissions.rs @@ -5,12 +5,16 @@ use std::{ sync::{Arc, RwLock}, }; -pub use deno_permissions::{CheckedPath, PermissionCheckError, PermissionDeniedError}; +pub use deno_permissions::{ + CheckedPath, PermissionCheckError, PermissionDeniedError, PermissionState, +}; pub fn oops(msg: impl std::fmt::Display) -> PermissionCheckError { PermissionCheckError::PermissionDenied(PermissionDeniedError { access: msg.to_string(), name: "web", + custom_message: None, + state: PermissionState::Denied, }) } @@ -156,13 +160,13 @@ impl AllowlistWebPermissions { Self(Arc::new(RwLock::new(AllowlistWebPermissionsSet::default()))) } - fn borrow(&self) -> std::sync::RwLockReadGuard { - self.0.read().expect("Could not lock permissions") - } - - fn borrow_mut(&self) -> std::sync::RwLockWriteGuard { - self.0.write().expect("Could not lock permissions") - } + fn borrow(&self) -> std::sync::RwLockReadGuard<'_, AllowlistWebPermissionsSet> { + self.0.read().expect("Could not lock permissions") + } + + fn borrow_mut(&self) -> std::sync::RwLockWriteGuard<'_, AllowlistWebPermissionsSet> { + self.0.write().expect("Could not lock permissions") + } /// Set the `hrtime` permission /// @@ -632,84 +636,6 @@ impl_sys_permission_kinds!( Inspector("inspector"), ); -#[derive(Clone, Debug)] -pub struct PermissionsContainer(pub Arc); -impl deno_web::TimersPermission for PermissionsContainer { - fn allow_hrtime(&mut self) -> bool { - self.0.allow_hrtime() - } -} -impl deno_fetch::FetchPermissions for PermissionsContainer { - fn check_net_url( - &mut self, - url: &reqwest::Url, - api_name: &str, - ) -> Result<(), PermissionCheckError> { - self.0.check_url(url, api_name)?; - Ok(()) - } - - fn check_open<'a>( - &mut self, - path: Cow<'a, Path>, - open_access: deno_permissions::OpenAccessKind, - api_name: &str, - ) -> Result, PermissionCheckError> { - let read = open_access.is_read(); - let write = open_access.is_write(); - - let p = self - .0 - .check_open(true, read, write, path, api_name) - .ok_or(oops("open"))?; - - Ok(CheckedPath::unsafe_new(p)) - } - - fn check_net_vsock( - &mut self, - cid: u32, - port: u32, - api_name: &str, - ) -> Result<(), PermissionCheckError> { - self.0.check_vsock(cid, port, api_name)?; - Ok(()) - } -} -impl deno_net::NetPermissions for PermissionsContainer { - fn check_net>( - &mut self, - host: &(T, Option), - api_name: &str, - ) -> Result<(), PermissionCheckError> { - self.0.check_host(host.0.as_ref(), host.1, api_name)?; - Ok(()) - } - - fn check_open<'a>( - &mut self, - path: Cow<'a, Path>, - open_access: deno_permissions::OpenAccessKind, - api_name: &str, - ) -> Result, PermissionCheckError> { - let read = open_access.is_read(); - let write = open_access.is_write(); - - let p = self - .0 - .check_open(true, read, write, path, api_name) - .ok_or(oops("open"))?; - - Ok(CheckedPath::unsafe_new(p)) - } - - fn check_vsock( - &mut self, - cid: u32, - port: u32, - api_name: &str, - ) -> Result<(), PermissionCheckError> { - self.0.check_vsock(cid, port, api_name)?; - Ok(()) - } -} +#[allow(dead_code)] +#[derive(Clone, Debug)] +pub struct PermissionsContainer(pub Arc); diff --git a/src/ext/web_stub/00_url.js b/src/ext/web_stub/00_url.js new file mode 100644 index 0000000..2027940 --- /dev/null +++ b/src/ext/web_stub/00_url.js @@ -0,0 +1,10 @@ +// Minimal URL implementation for web_stub +// Uses the native V8 URL implementation + +// V8 has built-in URL and URLSearchParams +// We just need to export them properly + +const URL = globalThis.URL; +const URLSearchParams = globalThis.URLSearchParams; + +export { URL, URLSearchParams }; diff --git a/src/ext/web_stub/01_console.js b/src/ext/web_stub/01_console.js new file mode 100644 index 0000000..4d00f4f --- /dev/null +++ b/src/ext/web_stub/01_console.js @@ -0,0 +1,156 @@ +// Minimal console implementation for web_stub +// This provides the Console class and inspect function that init_console.js needs + +import { primordials } from "ext:core/mod.js"; +const { + ObjectAssign, + ObjectFromEntries, + ArrayPrototypeMap, + ObjectGetOwnPropertyDescriptor, +} = primordials; + +const inspectOptions = { + depth: 4, + colors: false, + iteratorLimit: 100, + maxStringLength: Infinity, +}; + +function inspect(value, options = {}) { + const opts = { ...inspectOptions, ...options }; + return formatValue(value, opts.depth, new Set()); +} + +// Creates a proxy object that filters properties for inspect +// This is used by DOMException and other objects for custom inspect +function createFilteredInspectProxy({ object, evaluate, keys }) { + if (!evaluate) { + return object; + } + const descriptors = ObjectFromEntries( + ArrayPrototypeMap(keys, (key) => { + return [key, ObjectGetOwnPropertyDescriptor(object, key) ?? { + value: object[key], + enumerable: true, + }]; + }), + ); + return ObjectAssign({}, descriptors); +} + +function formatValue(value, depth, seen) { + if (value === null) return 'null'; + if (value === undefined) return 'undefined'; + + const type = typeof value; + + if (type === 'string') return JSON.stringify(value); + if (type === 'number' || type === 'boolean') return String(value); + if (type === 'bigint') return `${value}n`; + if (type === 'symbol') return value.toString(); + if (type === 'function') return `[Function: ${value.name || 'anonymous'}]`; + + if (type === 'object') { + if (seen.has(value)) return '[Circular]'; + seen.add(value); + + if (depth <= 0) return '[Object]'; + + if (Array.isArray(value)) { + const items = value.map(v => formatValue(v, depth - 1, seen)); + return `[ ${items.join(', ')} ]`; + } + + if (value instanceof Error) { + return value.stack || value.message || String(value); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (value instanceof RegExp) { + return value.toString(); + } + + const entries = Object.entries(value) + .map(([k, v]) => `${k}: ${formatValue(v, depth - 1, seen)}`) + .join(', '); + return `{ ${entries} }`; + } + + return String(value); +} + +class Console { + #printFn; + + constructor(printFn) { + this.#printFn = printFn; + } + + #print(level, ...args) { + const msg = args.map(a => typeof a === 'string' ? a : inspect(a)).join(' ') + '\n'; + this.#printFn(msg, level); + } + + log(...args) { this.#print(1, ...args); } + debug(...args) { this.#print(0, ...args); } + info(...args) { this.#print(1, ...args); } + warn(...args) { this.#print(2, ...args); } + error(...args) { this.#print(3, ...args); } + + dir(obj, options) { this.log(inspect(obj, options)); } + dirxml(...args) { this.log(...args); } + + assert(condition, ...args) { + if (!condition) { + this.error('Assertion failed:', ...args); + } + } + + clear() { /* no-op in stub */ } + + count(label = 'default') { + this.#counts.set(label, (this.#counts.get(label) || 0) + 1); + this.log(`${label}: ${this.#counts.get(label)}`); + } + #counts = new Map(); + + countReset(label = 'default') { + this.#counts.delete(label); + } + + group(...args) { this.log(...args); } + groupCollapsed(...args) { this.log(...args); } + groupEnd() { /* no-op */ } + + table(data) { this.log(inspect(data)); } + + time(label = 'default') { + this.#timers.set(label, performance.now()); + } + #timers = new Map(); + + timeEnd(label = 'default') { + const start = this.#timers.get(label); + if (start !== undefined) { + this.log(`${label}: ${(performance.now() - start).toFixed(3)}ms`); + this.#timers.delete(label); + } + } + + timeLog(label = 'default', ...args) { + const start = this.#timers.get(label); + if (start !== undefined) { + this.log(`${label}: ${(performance.now() - start).toFixed(3)}ms`, ...args); + } + } + + trace(...args) { + const err = new Error(); + this.log(...args, '\n', err.stack); + } +} + +export { Console, inspect, createFilteredInspectProxy }; diff --git a/src/ext/web_stub/01_dom_exception.js b/src/ext/web_stub/01_dom_exception.js index 5966cc7..ca65c27 100644 --- a/src/ext/web_stub/01_dom_exception.js +++ b/src/ext/web_stub/01_dom_exception.js @@ -15,7 +15,7 @@ const { } = primordials; import * as webidl from "ext:deno_webidl/00_webidl.js"; -import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; +import { createFilteredInspectProxy } from "ext:deno_web/01_console.js"; const _name = Symbol("name"); const _message = Symbol("message"); diff --git a/src/ext/web_stub/01_urlpattern.js b/src/ext/web_stub/01_urlpattern.js new file mode 100644 index 0000000..b1fafc3 --- /dev/null +++ b/src/ext/web_stub/01_urlpattern.js @@ -0,0 +1,64 @@ +// Minimal URLPattern implementation for web_stub +// This is a simplified version that may not support all features + +class URLPattern { + #pattern; + #baseURL; + + constructor(input, baseURL) { + if (typeof input === 'string') { + this.#pattern = input; + this.#baseURL = baseURL; + } else if (typeof input === 'object' && input !== null) { + this.#pattern = input; + this.#baseURL = baseURL; + } else { + throw new TypeError('Invalid URLPattern input'); + } + } + + test(input, baseURL) { + try { + this.exec(input, baseURL); + return true; + } catch { + return false; + } + } + + exec(input, baseURL) { + // Simplified implementation - just check if URL is valid + let url; + if (typeof input === 'string') { + url = new URL(input, baseURL); + } else if (input instanceof URL) { + url = input; + } else { + return null; + } + + // Return a basic result structure + return { + inputs: [input], + protocol: { input: url.protocol.slice(0, -1), groups: {} }, + username: { input: url.username, groups: {} }, + password: { input: url.password, groups: {} }, + hostname: { input: url.hostname, groups: {} }, + port: { input: url.port, groups: {} }, + pathname: { input: url.pathname, groups: {} }, + search: { input: url.search.slice(1), groups: {} }, + hash: { input: url.hash.slice(1), groups: {} }, + }; + } + + get protocol() { return this.#pattern?.protocol ?? '*'; } + get username() { return this.#pattern?.username ?? '*'; } + get password() { return this.#pattern?.password ?? '*'; } + get hostname() { return this.#pattern?.hostname ?? '*'; } + get port() { return this.#pattern?.port ?? '*'; } + get pathname() { return this.#pattern?.pathname ?? '*'; } + get search() { return this.#pattern?.search ?? '*'; } + get hash() { return this.#pattern?.hash ?? '*'; } +} + +export { URLPattern }; diff --git a/src/ext/web_stub/mod.rs b/src/ext/web_stub/mod.rs index 906dfa0..f3a2f05 100644 --- a/src/ext/web_stub/mod.rs +++ b/src/ext/web_stub/mod.rs @@ -17,7 +17,7 @@ extension!( encoding::op_base64_decode, encoding::op_base64_atob, encoding::op_base64_encode, encoding::op_base64_btoa, ], esm_entry_point = "ext:deno_web/init_stub.js", - esm = [ dir "src/ext/web_stub", "init_stub.js", "01_dom_exception.js", "02_timers.js", "05_base64.js" ], + esm = [ dir "src/ext/web_stub", "init_stub.js", "00_url.js", "01_urlpattern.js", "01_console.js", "01_dom_exception.js", "02_timers.js", "05_base64.js" ], state = |state| { state.put(StartTime::default()); } diff --git a/src/ext/webidl/mod.rs b/src/ext/webidl/mod.rs index 1fff97d..90b20a4 100644 --- a/src/ext/webidl/mod.rs +++ b/src/ext/webidl/mod.rs @@ -21,7 +21,7 @@ impl ExtensionTrait<()> for deno_webidl::deno_webidl { pub fn extensions(is_snapshot: bool) -> Vec { vec![ - deno_webidl::deno_webidl::build((), is_snapshot), - init_webidl::build((), is_snapshot), + >::build((), is_snapshot), + >::build((), is_snapshot), ] } diff --git a/src/ext/websocket/mod.rs b/src/ext/websocket/mod.rs index b561649..d5cf924 100644 --- a/src/ext/websocket/mod.rs +++ b/src/ext/websocket/mod.rs @@ -1,14 +1,6 @@ -use deno_core::{extension, url::Url, Extension}; -use deno_permissions::PermissionCheckError; +use deno_core::{extension, Extension}; -use super::{web::PermissionsContainer, web::WebOptions, ExtensionTrait}; - -impl deno_websocket::WebSocketPermissions for PermissionsContainer { - fn check_net_url(&mut self, url: &Url, api_name: &str) -> Result<(), PermissionCheckError> { - self.0.check_url(url, api_name)?; - Ok(()) - } -} +use super::ExtensionTrait; extension!( init_websocket, @@ -16,24 +8,22 @@ extension!( esm_entry_point = "ext:init_websocket/init_websocket.js", esm = [ dir "src/ext/websocket", "init_websocket.js" ], ); + impl ExtensionTrait<()> for init_websocket { fn init((): ()) -> Extension { init_websocket::init() } } -impl ExtensionTrait for deno_websocket::deno_websocket { - fn init(options: WebOptions) -> Extension { - deno_websocket::deno_websocket::init::( - options.user_agent, - options.root_cert_store_provider, - options.unsafely_ignore_certificate_errors, - ) + +impl ExtensionTrait<()> for deno_websocket::deno_websocket { + fn init((): ()) -> Extension { + deno_websocket::deno_websocket::init() } } -pub fn extensions(options: WebOptions, is_snapshot: bool) -> Vec { +pub fn extensions(is_snapshot: bool) -> Vec { vec![ - deno_websocket::deno_websocket::build(options, is_snapshot), + deno_websocket::deno_websocket::build((), is_snapshot), init_websocket::build((), is_snapshot), ] } diff --git a/src/inner_runtime.rs b/src/inner_runtime.rs index 0da2093..9b3d0e5 100644 --- a/src/inner_runtime.rs +++ b/src/inner_runtime.rs @@ -1,1406 +1,1486 @@ -use std::{ - collections::{HashMap, HashSet}, - path::{Path, PathBuf}, - pin::Pin, - rc::Rc, - sync::Arc, - task::Poll, - time::Duration, -}; - -use deno_core::{ - futures::FutureExt, serde_json, serde_v8::from_v8, v8, JsRuntime, JsRuntimeForSnapshot, - PollEventLoopOptions, -}; -use deno_features::FeatureChecker; -use serde::de::DeserializeOwned; -use tokio_util::sync::CancellationToken; - -use crate::{ - ext, - module_loader::{LoaderOptions, RustyLoader}, - traits::{ToDefinedValue, ToModuleSpecifier, ToV8String}, - transpiler::transpile, - utilities, Error, ExtensionOptions, Module, ModuleHandle, -}; - -/// Wrapper trait to make the `InnerRuntime` generic over the runtime types -pub trait RuntimeTrait { - fn try_new(options: deno_core::RuntimeOptions) -> Result - where - Self: Sized; - fn rt_mut(&mut self) -> &mut JsRuntime; -} -impl RuntimeTrait for JsRuntime { - fn try_new(options: deno_core::RuntimeOptions) -> Result - where - Self: Sized, - { - let rt = Self::try_new(options)?; - Ok(rt) - } - fn rt_mut(&mut self) -> &mut JsRuntime { - self - } -} -impl RuntimeTrait for JsRuntimeForSnapshot { - fn try_new(options: deno_core::RuntimeOptions) -> Result - where - Self: Sized, - { - let rt = Self::try_new(options)?; - Ok(rt) - } - fn rt_mut(&mut self) -> &mut JsRuntime { - self - } -} - -/// Represents a function that can be registered with the runtime -pub trait RsFunction: - Fn(&[serde_json::Value]) -> Result + 'static -{ -} -impl RsFunction for F where - F: Fn(&[serde_json::Value]) -> Result + 'static -{ -} - -/// Represents an async function that can be registered with the runtime -pub trait RsAsyncFunction: - Fn( - Vec, - ) -> Pin>>> - + 'static -{ -} -impl RsAsyncFunction for F where - F: Fn( - Vec, - ) -> Pin>>> - + 'static -{ -} - -/// Decodes a set of arguments into a vector of v8 values -/// This is used to pass arguments to a javascript function -/// And is faster and more flexible than using `json_args!` -fn decode_args<'a>( - args: &impl serde::ser::Serialize, - scope: &mut v8::HandleScope<'a>, -) -> Result>, Error> { - let args = deno_core::serde_v8::to_v8(scope, args)?; - match v8::Local::::try_from(args) { - Ok(args) => { - let len = args.length(); - let mut result = Vec::with_capacity(len as usize); - for i in 0..len { - let index = v8::Integer::new( - scope, - i.try_into().map_err(|_| { - Error::Runtime(format!( - "Could not decode {len} arguments - use `big_json_args`" - )) - })?, - ); - let arg = args - .get(scope, index.into()) - .ok_or_else(|| Error::Runtime(format!("Invalid argument at index {i}")))?; - result.push(arg); - } - Ok(result) - } - Err(_) if args.is_undefined() || args.is_null() => Ok(vec![]), - Err(_) => Ok(vec![args]), - } -} - -/// Represents the set of options accepted by the runtime constructor -pub struct RuntimeOptions { - /// A set of `deno_core` extensions to add to the runtime - pub extensions: Vec, - - /// Additional options for the built-in extensions - pub extension_options: ext::ExtensionOptions, - - /// Function to use as entrypoint if the module does not provide one - pub default_entrypoint: Option, - - /// Amount of time to run for before killing the thread - pub timeout: Duration, - - /// Optional maximum heap size for the runtime - /// - /// If the heap size is exceeded, the runtime will return a `HeapExhausted` error. - /// - /// **WARNING** this is not a minimum heap size; the underlying V8 isolate will still crash if this number is too small for startup - /// (~5mb with default features) - pub max_heap_size: Option, - - /// Optional cache provider for the module loader - #[allow(deprecated)] - pub module_cache: Option>, - - /// Optional import provider for the module loader - pub import_provider: Option>, - - /// Optional snapshot to load into the runtime - /// - /// This will reduce load times, but requires the same extensions to be loaded as when the snapshot was created - /// - /// WARNING: Snapshots MUST be used on the same system they were created on - pub startup_snapshot: Option<&'static [u8]>, - - /// Optional configuration parameters for building the underlying v8 isolate - /// - /// This can be used to alter the behavior of the runtime. - /// - /// See the `rusty_v8` documentation for more information - pub isolate_params: Option, - - /// Optional shared array buffer store to use for the runtime. - /// - /// Allows data-sharing between runtimes across threads - pub shared_array_buffer_store: Option, - - /// A whitelist of custom schema prefixes that are allowed to be loaded from javascript - /// - /// By default only `http`/`https` (`url_import` crate feature), and `file` (`fs_import` crate feature) are allowed - pub schema_whlist: HashSet, -} - -impl Default for RuntimeOptions { - fn default() -> Self { - Self { - extensions: Vec::default(), - default_entrypoint: None, - timeout: Duration::MAX, - max_heap_size: None, - module_cache: None, - import_provider: None, - startup_snapshot: None, - isolate_params: None, - shared_array_buffer_store: None, - schema_whlist: HashSet::default(), - - extension_options: ExtensionOptions::default(), - } - } -} - -/// Deno `JsRuntime` wrapper providing helper functions needed -/// by the public-facing Runtime API -/// -/// This struct is not intended to be used directly by the end user -/// It provides a set of async functions that can be used to interact with the -/// underlying deno runtime instance -pub struct InnerRuntime { - pub module_loader: Rc, - pub deno_runtime: RT, - - pub cwd: PathBuf, - pub default_entrypoint: Option, -} -impl InnerRuntime { - pub fn new( - options: RuntimeOptions, - heap_exhausted_token: CancellationToken, - ) -> Result { - let cwd = std::env::current_dir()?; - let module_loader = Rc::new(RustyLoader::new(LoaderOptions { - cache_provider: options.module_cache, - import_provider: options.import_provider, - schema_whlist: options.schema_whlist, - cwd: cwd.clone(), - - #[cfg(feature = "node_experimental")] - node_resolver: options.extension_options.node_resolver.clone(), - - ..Default::default() - })); - - // If a snapshot is provided, do not reload ESM for extensions - let is_snapshot = options.startup_snapshot.is_some(); - let extensions = ext::all_extensions( - options.extensions, - options.extension_options, - options.shared_array_buffer_store.clone(), - is_snapshot, - ); - - // If a heap size is provided, set the isolate params (preserving any user-provided params otherwise) - let isolate_params = match options.isolate_params { - Some(params) => { - if let Some(max_heap_size) = options.max_heap_size { - Some(params.heap_limits(0, max_heap_size)) - } else { - Some(params) - } - } - None => { - if let Some(max_heap_size) = options.max_heap_size { - let params = v8::Isolate::create_params().heap_limits(0, max_heap_size); - Some(params) - } else { - None - } - } - }; - - let mut deno_runtime = RT::try_new(deno_core::RuntimeOptions { - module_loader: Some(module_loader.clone()), - - extension_transpiler: Some(module_loader.as_extension_transpiler()), - create_params: isolate_params, - shared_array_buffer_store: options.shared_array_buffer_store.clone(), - - startup_snapshot: options.startup_snapshot, - extensions, - - ..Default::default() - })?; - - let mut feature_checker = FeatureChecker::default(); - feature_checker.set_exit_cb(Box::new(|_, _| {})); - deno_runtime - .rt_mut() - .op_state() - .borrow_mut() - .put(Arc::new(feature_checker)); - - // Add a callback to terminate the runtime if the max_heap_size limit is approached - if options.max_heap_size.is_some() { - let isolate_handle = deno_runtime.rt_mut().v8_isolate().thread_safe_handle(); - - deno_runtime - .rt_mut() - .add_near_heap_limit_callback(move |current_value, _| { - isolate_handle.terminate_execution(); - - // Signal the outer runtime to cancel block_on future (avoid hanging) and return friendly error - heap_exhausted_token.cancel(); - - // Spike the heap limit while terminating to avoid segfaulting - // Callback may fire multiple times if memory usage increases quicker then termination finalizes - 5 * current_value - }); - } - - let default_entrypoint = options.default_entrypoint; - Ok(Self { - module_loader, - deno_runtime, - cwd, - default_entrypoint, - }) - } - - /// Destroy the `RustyScript` runtime, returning the deno RT instance - #[allow(dead_code)] - pub fn into_inner(self) -> RT { - self.deno_runtime - } - - /// Access the underlying deno runtime instance directly - pub fn deno_runtime(&mut self) -> &mut JsRuntime { - self.deno_runtime.rt_mut() - } - - /// Set the current working directory for the runtime - /// This is used to resolve relative paths in the module loader - pub fn set_current_dir(&mut self, path: impl AsRef) -> Result<&Path, Error> { - let path = path.as_ref(); - let path = utilities::resolve_path(path, Some(&self.cwd))? - .to_file_path() - .map_err(|()| Error::Runtime("Invalid path".to_string()))?; - - self.cwd = path; - self.module_loader.set_current_dir(self.cwd.clone()); - Ok(&self.cwd) - } - - pub fn current_dir(&self) -> &Path { - &self.cwd - } - - /// Remove and return a value from the state - pub fn take(&mut self) -> Option - where - T: 'static, - { - let state = self.deno_runtime().op_state(); - if let Ok(mut state) = state.try_borrow_mut() { - if state.has::() { - return Some(state.take()); - } - } - - None - } - - /// Add a value to the state - /// Only one value of each type is stored - pub fn put(&mut self, value: T) -> Result<(), Error> - where - T: 'static, - { - let state = self.deno_runtime().op_state(); - let mut state = state.try_borrow_mut()?; - state.put(value); - - Ok(()) - } - - /// Register an async rust function - /// The function must return a Future that resolves to a `serde_json::Value` - /// and accept a vec of `serde_json::Value` as arguments - pub fn register_async_function(&mut self, name: &str, callback: F) -> Result<(), Error> - where - F: RsAsyncFunction, - { - let state = self.deno_runtime().op_state(); - let mut state = state.try_borrow_mut()?; - - if !state.has::>>() { - state.put(HashMap::>::new()); - } - - // Insert the callback into the state - state - .borrow_mut::>>() - .insert(name.to_string(), Box::new(callback)); - - Ok(()) - } - - /// Register a rust function - /// The function must return a `serde_json::Value` - /// and accept a slice of `serde_json::Value` as arguments - pub fn register_function(&mut self, name: &str, callback: F) -> Result<(), Error> - where - F: RsFunction, - { - let state = self.deno_runtime().op_state(); - let mut state = state.try_borrow_mut()?; - - if !state.has::>>() { - state.put(HashMap::>::new()); - } - - // Insert the callback into the state - state - .borrow_mut::>>() - .insert(name.to_string(), Box::new(callback)); - - Ok(()) - } - - /// Runs the JS event loop to completion - pub async fn await_event_loop( - &mut self, - options: PollEventLoopOptions, - timeout: Option, - ) -> Result<(), Error> { - if let Some(timeout) = timeout { - Ok(tokio::select! { - r = self.deno_runtime().run_event_loop(options) => r, - () = tokio::time::sleep(timeout) => Ok(()), - }?) - } else { - Ok(self.deno_runtime().run_event_loop(options).await?) - } - } - - /// Advances the JS event loop by one tick - /// Return true if the event loop is pending - pub async fn advance_event_loop( - &mut self, - options: PollEventLoopOptions, - ) -> Result { - let result = std::future::poll_fn(|cx| { - Poll::Ready(match self.deno_runtime().poll_event_loop(cx, options) { - Poll::Ready(t) => t.map(|()| false), - Poll::Pending => Ok(true), - }) - }) - .await?; - - Ok(result) - } - - /// Evaluate a piece of non-ECMAScript-module JavaScript code - /// The expression is evaluated in the global context, so changes persist - /// - /// Async because some expressions may require a tokio runtime - /// - /// # Arguments - /// * `expr` - A string representing the JavaScript expression to evaluate - /// - /// # Returns - /// A `Result` containing the deserialized result of the expression (`T`) - /// or an error (`Error`) if the expression cannot be evaluated or if the - /// result cannot be deserialized. - #[allow(clippy::unused_async, reason = "Prevent panic on sleep calls")] - pub async fn eval(&mut self, expr: impl ToString) -> Result, Error> { - let result = self.deno_runtime().execute_script("", expr.to_string())?; - Ok(result) - } - - /// Attempt to get a value out of the global context (globalThis.name) - /// - /// # Arguments - /// * `name` - Name of the object to extract - /// - /// # Returns - /// A `Result` containing the non-null value extracted or an error (`Error`) - pub fn get_global_value(&mut self, name: &str) -> Result, Error> { - let context = self.deno_runtime().main_context(); - let mut scope = self.deno_runtime().handle_scope(); - let global = context.open(&mut scope).global(&mut scope); - - let key = name.to_v8_string(&mut scope)?; - let value = global.get(&mut scope, key.into()); - - match value.if_defined() { - Some(v) => Ok(v8::Global::::new(&mut scope, v)), - _ => Err(Error::ValueNotFound(name.to_string())), - } - } - - /// Attempt to get a value out of a module context - /// /// - /// # Arguments - /// * `module` - A handle to a loaded module - /// * `name` - Name of the object to extract - /// - /// # Returns - /// A `Result` containing the non-null value extracted or an error (`Error`) - pub fn get_module_export_value( - &mut self, - module_context: &ModuleHandle, - name: &str, - ) -> Result, Error> { - let module_namespace = self - .deno_runtime() - .get_module_namespace(module_context.id())?; - let mut scope = self.deno_runtime().handle_scope(); - let module_namespace = module_namespace.open(&mut scope); - assert!(module_namespace.is_module_namespace_object()); - - let key = name.to_v8_string(&mut scope)?; - let value = module_namespace.get(&mut scope, key.into()); - - match value.if_defined() { - Some(v) => Ok(v8::Global::::new(&mut scope, v)), - _ => Err(Error::ValueNotFound(name.to_string())), - } - } - - pub async fn resolve_with_event_loop( - &mut self, - value: v8::Global, - ) -> Result, Error> { - let future = self.deno_runtime().resolve(value); - let result = self - .deno_runtime() - .with_event_loop_future(future, PollEventLoopOptions::default()) - .await?; - Ok(result) - } - - pub fn decode_value(&mut self, value: v8::Global) -> Result - where - T: DeserializeOwned, - { - let mut scope = self.deno_runtime().handle_scope(); - let result = v8::Local::::new(&mut scope, value); - Ok(from_v8(&mut scope, result)?) - } - - pub fn get_value_ref( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - ) -> Result, Error> { - // Try to get the value from the module context first - let result = module_context - .and_then(|module_context| self.get_module_export_value(module_context, name).ok()); - - // If it's not found, try the global context - match result { - Some(result) => Ok(result), - None => self - .get_global_value(name) - .map_err(|_| Error::ValueNotFound(name.to_string())), - } - } - - /// Retrieves a javascript function by its name from the Deno runtime's global context. - /// - /// # Arguments - /// * `module_context` - A module handle to use for context, to find exports - /// * `name` - A string representing the name of the javascript function to retrieve. - /// - /// # Returns - /// A `Result` containing a `v8::Global` if - /// the function is found, or an error (`Error`) if the function cannot be found or - /// if it is not a valid javascript function. - pub fn get_function_by_name( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - ) -> Result, Error> { - // Get the value - let value = self.get_value_ref(module_context, name)?; - - // Convert it into a function - let mut scope = self.deno_runtime().handle_scope(); - let local_value = v8::Local::::new(&mut scope, value); - let f: v8::Local = local_value - .try_into() - .or::(Err(Error::ValueNotCallable(name.to_string())))?; - - // Return it as a global - Ok(v8::Global::::new(&mut scope, f)) - } - - pub fn call_function_by_ref( - &mut self, - module_context: Option<&ModuleHandle>, - function: &v8::Global, - args: &impl serde::ser::Serialize, - ) -> Result, Error> { - // Namespace, if provided - let module_namespace = if let Some(module_context) = module_context { - Some( - self.deno_runtime() - .get_module_namespace(module_context.id())?, - ) - } else { - None - }; - - let mut scope = self.deno_runtime().handle_scope(); - let mut scope = v8::TryCatch::new(&mut scope); - - // Get the namespace - // Module-level if supplied, none otherwise - let namespace: v8::Local = if let Some(namespace) = module_namespace { - v8::Local::::new(&mut scope, namespace).into() - } else { - // Create a new object to use as the namespace if none is provided - //let obj: v8::Local = v8::Object::new(&mut scope).into(); - let obj: v8::Local = v8::undefined(&mut scope).into(); - obj - }; - - let function_instance = function.open(&mut scope); - - // Prep arguments - let args = decode_args(args, &mut scope)?; - - // Call the function - let result = function_instance.call(&mut scope, namespace, &args); - match result { - Some(value) => { - let value = v8::Global::new(&mut scope, value); - Ok(value) - } - None if scope.has_caught() => { - let e = scope - .message() - .ok_or_else(|| Error::Runtime("Unknown error".to_string()))?; - - let filename = e.get_script_resource_name(&mut scope); - let linenumber = e.get_line_number(&mut scope).unwrap_or_default(); - let filename = if let Some(v) = filename { - let filename = v.to_rust_string_lossy(&mut scope); - format!("{filename}:{linenumber}: ") - } else if let Some(module_context) = module_context { - let filename = module_context.module().filename().to_string_lossy(); - format!("{filename}:{linenumber}: ") - } else { - String::new() - }; - - let msg = e.get(&mut scope).to_rust_string_lossy(&mut scope); - - let s = format!("{filename}{msg}"); - Err(Error::Runtime(s)) - } - None => Err(Error::Runtime( - "Unknown error during function execution".to_string(), - )), - } - } - - /// A utility function that run provided future concurrently with the event loop. - /// - /// If the event loop resolves while polling the future, it will continue to be polled, - /// Unless it returned an error - /// - /// Useful for interacting with local inspector session. - pub async fn with_event_loop_future<'fut, T, E>( - &mut self, - mut fut: impl std::future::Future> + Unpin + 'fut, - poll_options: PollEventLoopOptions, - ) -> Result - where - deno_core::error::AnyError: From, - Error: std::convert::From, - { - // Manually implement tokio::select - std::future::poll_fn(|cx| { - let evt_status = self.deno_runtime().poll_event_loop(cx, poll_options); - let fut_status = fut.poll_unpin(cx); - - match (evt_status, fut_status) { - (Poll::Ready(Err(e)), _) => { - // Event loop failed - Poll::Ready(Err(e.into())) - } - - (_, Poll::Pending) => { - // Continue polling - Poll::Pending - } - - (_, Poll::Ready(t)) => { - for _ in 0..100 { - if let Poll::Ready(Err(e)) = - self.deno_runtime().poll_event_loop(cx, poll_options) - { - return Poll::Ready(Err(e.into())); - } - } - - // Future resolved - Poll::Ready(t.map_err(Into::into)) - } - } - }) - .await - } - - /// Get the entrypoint function for a module - pub fn get_module_entrypoint( - &mut self, - module_context: &mut ModuleHandle, - ) -> Result>, Error> { - let default = self.default_entrypoint.clone(); - - // Try to get an entrypoint from a call to `rustyscript.register_entrypoint` first - let state = self.deno_runtime().op_state(); - let mut deep_state = state.try_borrow_mut()?; - let entrypoint = deep_state.try_take::>(); - if let Some(entrypoint) = entrypoint { - return Ok(Some(entrypoint)); - } - - // Try to get an entrypoint from the default export next - if let Ok(default_export) = self.get_module_export_value(module_context, "default") { - let mut scope = self.deno_runtime().handle_scope(); - let default_export = v8::Local::new(&mut scope, default_export); - if default_export.is_function() { - if let Ok(f) = v8::Local::::try_from(default_export) { - return Ok(Some(v8::Global::new(&mut scope, f))); - } - } - } - - // Try to get an entrypoint from the default entrypoint - if let Some(default) = default.as_deref() { - if let Ok(f) = self.get_function_by_name(Some(module_context), default) { - return Ok(Some(f)); - } - } - - Ok(None) - } - - /// Load one or more modules - /// Returns a future that resolves to a handle to the main module, or the last - /// side-module - /// - /// Will return a handle to the main module, or the last - /// side-module - pub async fn load_modules( - &mut self, - main_module: Option<&Module>, - side_modules: Vec<&Module>, - ) -> Result { - if main_module.is_none() && side_modules.is_empty() { - return Err(Error::Runtime( - "Internal error: attempt to load no modules".to_string(), - )); - } - - let mut module_handle_stub = ModuleHandle::default(); - - // Get additional modules first - for side_module in side_modules { - let module_specifier = side_module.filename().to_module_specifier(&self.cwd)?; - let (code, sourcemap) = transpile(&module_specifier, side_module.contents())?; - - // Now CJS translation, for node - #[cfg(feature = "node_experimental")] - let code = self - .module_loader - .translate_cjs(&module_specifier, &code) - .await?; - - let fast_code = deno_core::FastString::from(code.clone()); - - let s_modid = self - .deno_runtime() - .load_side_es_module_from_code(&module_specifier, fast_code) - .await?; - - // Update source map cache - self.module_loader.insert_source_map( - module_specifier.as_str(), - code, - sourcemap.map(|s| s.to_vec()), - ); - - let mod_load = self.deno_runtime().mod_evaluate(s_modid); - self.with_event_loop_future(mod_load, PollEventLoopOptions::default()) - .await?; - module_handle_stub = ModuleHandle::new(side_module, s_modid, None); - } - - // Load main module - if let Some(module) = main_module { - let module_specifier = module.filename().to_module_specifier(&self.cwd)?; - let (code, sourcemap) = transpile(&module_specifier, module.contents())?; - - // Now CJS translation, for node - #[cfg(feature = "node_experimental")] - let code = self - .module_loader - .translate_cjs(&module_specifier, &code) - .await?; - - let fast_code = deno_core::FastString::from(code.clone()); - - let module_id = self - .deno_runtime() - .load_main_es_module_from_code(&module_specifier, fast_code) - .await?; - - // Update source map cache - self.module_loader.insert_source_map( - module_specifier.as_str(), - code, - sourcemap.map(|s| s.to_vec()), - ); - - // Finish execution - let mod_load = self.deno_runtime().mod_evaluate(module_id); - self.with_event_loop_future(mod_load, PollEventLoopOptions::default()) - .await?; - module_handle_stub = ModuleHandle::new(module, module_id, None); - } - - // Try to get the default entrypoint - let entrypoint = self.get_module_entrypoint(&mut module_handle_stub)?; - - Ok(ModuleHandle::new( - module_handle_stub.module(), - module_handle_stub.id(), - entrypoint, - )) - } -} - -#[cfg(test)] -mod test_inner_runtime { - use serde::Deserialize; - - use crate::{async_callback, big_json_args, js_value::Function, json_args, sync_callback}; - - #[cfg(any(feature = "web", feature = "web_stub"))] - use crate::js_value::Promise; - - use super::*; - - /// Used for blocking functions - fn run_async_task(f: F) -> T - where - U: std::future::Future>, - F: FnOnce() -> U, - { - let timeout = Duration::from_secs(2); - let tokio = tokio::runtime::Builder::new_current_thread() - .enable_all() - .thread_keep_alive(timeout) - .build() - .unwrap(); - tokio - .block_on(async move { - tokio::time::timeout(timeout, f()) - .await - .expect("Test failed") - }) - .expect("Timed out") - } - - macro_rules! assert_v8 { - ($l:expr, $r:expr, $t:ty, $rt:expr) => { - assert_eq!($rt.decode_value::<$t>($l).expect("Wrong type"), $r,) - }; - } - - #[test] - fn test_decode_args() { - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - let mut scope = runtime.deno_runtime.handle_scope(); - - // empty - let args = decode_args(&json_args!(), &mut scope).expect("Could not decode args"); - assert_eq!(args.len(), 0); - - // single - let args = decode_args(&json_args!(2), &mut scope).expect("Could not decode args"); - assert_eq!(args.len(), 1); - - // single raw - let args = decode_args(&2, &mut scope).expect("Could not decode args"); - assert_eq!(args.len(), 1); - - // multiple heterogeneous - let args = decode_args(&json_args!(2, "test"), &mut scope).expect("Could not decode args"); - assert_eq!(args.len(), 2); - - // multiple homogeneous - let args = decode_args(&json_args!(2, 3), &mut scope).expect("Could not decode args"); - assert_eq!(args.len(), 2); - - // 16 args - let args = decode_args( - &(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), - &mut scope, - ) - .expect("Could not decode args"); - assert_eq!(args.len(), 16); - - // 32 args - let args = decode_args( - &big_json_args!( - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15 - ), - &mut scope, - ) - .expect("Could not decode args"); - assert_eq!(args.len(), 32); - } - - #[test] - fn test_put_take() { - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - runtime.put(2usize).expect("Could not put value"); - let v = runtime.take::().expect("Could not take value"); - assert_eq!(v, 2); - } - - #[test] - fn test_register_async_function() { - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - runtime - .register_async_function( - "test", - async_callback!(|a: i64, b: i64| async move { Ok::(a + b) }), - ) - .expect("Could not register function"); - - let module = Module::new( - "test.js", - " - globalThis.v = await rustyscript.async_functions.test(2, 3); - ", - ); - - let rt = &mut runtime; - let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); - - let result = runtime - .get_value_ref(Some(&module), "v") - .expect("Could not find global"); - assert_v8!(result, 5, usize, runtime); - } - - #[test] - fn test_register_function() { - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - runtime - .register_function( - "test", - sync_callback!(|a: i64, b: i64| { Ok::(a + b) }), - ) - .expect("Could not register function"); - - run_async_task(|| async move { - let v = runtime - .eval("rustyscript.functions.test(2, 3)") - .await - .expect("failed to eval"); - assert_v8!(v, 5, usize, runtime); - Ok(()) - }); - } - - #[cfg(any(feature = "web", feature = "web_stub"))] - #[test] - fn test_eval() { - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - run_async_task(|| async move { - let v = runtime.eval("2 + 2").await.expect("failed to eval"); - assert_v8!(v, 4, usize, runtime); - let result = runtime - .eval( - " - let sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - sleep(500).then(() => 2); - ", - ) - .await - .expect("failed to eval"); - - let result: Promise = runtime - .decode_value(result) - .expect("Could not decode promise"); - - let result: u32 = result.resolve(runtime.deno_runtime()).await?; - assert_eq!(result, 2); - Ok(()) - }); - } - - #[cfg(feature = "web_stub")] - #[test] - fn test_base64() { - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - run_async_task(|| async move { - let result = runtime.eval("btoa('foo')").await.expect("failed to eval"); - assert_v8!(result, "Zm9v", String, runtime); - - let result = runtime - .eval("atob(btoa('foo'))") - .await - .expect("failed to eval"); - assert_v8!(result, "foo", String, runtime); - - Ok(()) - }); - } - - #[test] - fn test_get_value_ref() { - let module = Module::new( - "test.js", - " - globalThis.a = 2; - export const b = 'test'; - export const fnc = null; - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); - - let v = runtime - .get_value_ref(None, "a") - .expect("Could not find global"); - assert_v8!(v, 2, usize, runtime); - - let v = runtime - .get_value_ref(Some(&module), "a") - .expect("Could not find global"); - assert_v8!(v, 2, usize, runtime); - - let v = runtime - .get_value_ref(Some(&module), "b") - .expect("Could not find export"); - assert_v8!(v, "test", String, runtime); - - runtime - .get_value_ref(Some(&module), "c") - .expect_err("Could not detect null"); - - runtime - .get_value_ref(Some(&module), "d") - .expect_err("Could not detect undeclared"); - } - - #[test] - fn test_get_function_by_name() { - let module = Module::new( - "test.js", - " - globalThis.fna = () => {}; - export function fnb() {} - export const fnc = 2; - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); - - runtime - .get_function_by_name(Some(&module), "fna") - .expect("Did not find global"); - runtime - .get_function_by_name(Some(&module), "fnb") - .expect("Did not find export"); - runtime - .get_function_by_name(Some(&module), "fnc") - .expect_err("Did not detect non-function"); - runtime - .get_function_by_name(Some(&module), "fnd") - .expect_err("Did not detect undefined"); - } - - #[test] - fn test_call_function_by_ref() { - let module = Module::new( - "test.js", - " - globalThis.fna = (i) => i; - export function fnb() { - return 'test'; - } - export const fnc = 2; - export const fne = () => {}; - - export const will_err = () => { - throw new Error('msg'); - } - ", - ); - - run_async_task(|| async move { - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - let handle = runtime.load_modules(Some(&module), vec![]).await?; - - let f = runtime.get_function_by_name(None, "fna").unwrap(); - let result = runtime - .call_function_by_ref(Some(&handle), &f, json_args!(2)) - .expect("Could not call global"); - assert_v8!(result, 2, usize, runtime); - - let f = runtime.get_function_by_name(Some(&handle), "fnb").unwrap(); - let result = runtime - .call_function_by_ref(Some(&handle), &f, json_args!()) - .expect("Could not call export"); - assert_v8!(result, "test", String, runtime); - - let f = runtime.get_function_by_name(Some(&handle), "fne").unwrap(); - runtime - .call_function_by_ref(Some(&handle), &f, json_args!()) - .expect("Did not allow undefined return"); - - let f = runtime - .get_function_by_name(Some(&handle), "will_err") - .unwrap(); - runtime - .call_function_by_ref(Some(&handle), &f, json_args!()) - .expect_err("Did not catch error"); - - Ok(()) - }); - } - - #[test] - fn test_ts_loader() { - let module = Module::new( - "test.ts", - " - export function test(left:number, right:number): number { - return left + right; - } - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); - - let f = runtime.get_function_by_name(Some(&module), "test").unwrap(); - let rt = &mut runtime; - let result = run_async_task(|| async move { - rt.call_function_by_ref(Some(&module), &f, json_args!(2, 3)) - }); - assert_v8!(result, 5, usize, runtime); - } - - #[cfg(any(feature = "web", feature = "web_stub"))] - #[test] - fn test_toplevel_await() { - let module = Module::new( - "test.js", - " - const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - await sleep(100); - export function test() { - return 2; - } - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let module = run_async_task(|| async move { - let h = rt.load_modules(Some(&module), vec![]).await; - rt.await_event_loop(PollEventLoopOptions::default(), None) - .await?; - h - }); - - let f = runtime.get_function_by_name(Some(&module), "test").unwrap(); - let rt = &mut runtime; - let result = - run_async_task( - || async move { rt.call_function_by_ref(Some(&module), &f, json_args!()) }, - ); - assert_v8!(result, 2, usize, runtime); - } - - #[cfg(any(feature = "web", feature = "web_stub"))] - #[test] - fn test_promise() { - let module = Module::new( - "test.js", - " - export const test = () => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(2); - }, 50); - }); - } - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - run_async_task(|| async move { - let module = rt.load_modules(Some(&module), vec![]).await?; - - let f = rt.get_function_by_name(Some(&module), "test").unwrap(); - let result = rt.call_function_by_ref(Some(&module), &f, json_args!())?; - - let result = rt.resolve_with_event_loop(result).await?; - assert_v8!(result, 2, usize, rt); - - Ok(()) - }); - } - - #[cfg(any(feature = "web", feature = "web_stub"))] - #[test] - fn test_async_fn() { - let module = Module::new( - "test.js", - " - const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - export async function test() { - await sleep(100); - return 2; - } - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - run_async_task(|| async move { - let module = rt.load_modules(Some(&module), vec![]).await?; - - let f = rt.get_function_by_name(Some(&module), "test")?; - let result = rt.call_function_by_ref(Some(&module), &f, json_args!())?; - let result: Promise = rt.decode_value(result).expect("Could not deserialize"); - let result: usize = result.resolve(rt.deno_runtime()).await?; - assert_eq!(2, result); - - Ok(()) - }); - } - - #[test] - fn test_deep_error() { - let module = Module::new( - "test.js", - " - await new Promise(r => setTimeout(r)); throw 'huh'; - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - run_async_task(|| async move { - let result = rt.load_modules(Some(&module), vec![]).await; - assert!(result.is_err()); - Ok(()) - }); - } - - #[test] - fn test_serialize_deep_fn() { - let module = Module::new( - "test.js", - " - let a = 2; - export const test = { - 'name': 'test', - 'func': (x) => x + a - } - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); - - #[derive(Deserialize)] - #[allow(clippy::items_after_statements)] - struct TestStruct { - #[allow(dead_code)] - name: String, - func: Function, - } - - let structure = runtime.get_value_ref(Some(&module), "test").unwrap(); - let structure: TestStruct = runtime - .decode_value(structure) - .expect("Could not deserialize"); - - let function = structure - .func - .as_global(&mut runtime.deno_runtime().handle_scope()); - - run_async_task(|| async move { - let value = runtime - .call_function_by_ref(Some(&module), &function, json_args!(2)) - .expect("could not call function"); - assert_v8!(value, 4, usize, runtime); - - let value = runtime - .call_function_by_ref(Some(&module), &function, json_args!(3)) - .expect("could not call function twice"); - assert_v8!(value, 5, usize, runtime); - - Ok(()) - }); - } - - #[test] - fn test_async_load_errors() { - let module = Module::new( - "test.js", - " - throw new Error('msg'); - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let module_ = module.clone(); - let result = - run_async_task( - || async move { Ok(rt.load_modules(Some(&module_), vec![]).await.is_err()) }, - ); - assert!(result); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let result = - run_async_task( - || async move { Ok(rt.load_modules(None, vec![&module]).await.is_err()) }, - ); - assert!(result); - } - - #[test] - fn test_serialize_fn() { - let module = Module::new( - "test.js", - " - export const test = (x) => 2*x; - ", - ); - - let mut runtime = - InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) - .expect("Could not load runtime"); - - let rt = &mut runtime; - let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); - - let function = runtime - .get_function_by_name(Some(&module), "test") - .expect("Could not get function"); - - run_async_task(|| async move { - let value = runtime - .call_function_by_ref(Some(&module), &function, json_args!(2)) - .expect("could not call function"); - assert_v8!(value, 4, usize, runtime); - - let value = runtime - .call_function_by_ref(None, &function, json_args!(2)) - .expect("could not call function"); - assert_v8!(value, 4, usize, runtime); - - Ok(()) - }); - } -} +use std::{ + collections::{HashMap, HashSet}, + path::{Path, PathBuf}, + pin::Pin, + rc::Rc, + sync::Arc, + task::Poll, + time::Duration, +}; + +use deno_core::{ + futures::FutureExt, serde_json, serde_v8::from_v8, v8, JsRuntime, JsRuntimeForSnapshot, + PollEventLoopOptions, +}; +use deno_features::FeatureChecker; +use serde::de::DeserializeOwned; +use tokio_util::sync::CancellationToken; + +use crate::{ + ext, + module_loader::{LoaderOptions, RustyLoader}, + traits::{ToDefinedValue, ToModuleSpecifier}, + transpiler::transpile, + utilities, Error, ExtensionOptions, Module, ModuleHandle, +}; + +fn safe_prepare_stack_trace_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + error: v8::Local<'s, v8::Value>, + _callsites: v8::Local<'s, v8::Array>, +) -> v8::Local<'s, v8::Value> { + let message = error.to_string(scope).map_or_else( + || "Uncaught Error".to_string(), + |s| s.to_rust_string_lossy(scope), + ); + + v8::String::new(scope, &message).map_or_else(|| v8::undefined(scope).into(), Into::into) +} + +/// Wrapper trait to make the `InnerRuntime` generic over the runtime types +pub trait RuntimeTrait { + fn try_new(options: deno_core::RuntimeOptions) -> Result + where + Self: Sized; + fn rt_mut(&mut self) -> &mut JsRuntime; +} +impl RuntimeTrait for JsRuntime { + fn try_new(options: deno_core::RuntimeOptions) -> Result + where + Self: Sized, + { + let rt = Self::try_new(options)?; + Ok(rt) + } + fn rt_mut(&mut self) -> &mut JsRuntime { + self + } +} +impl RuntimeTrait for JsRuntimeForSnapshot { + fn try_new(options: deno_core::RuntimeOptions) -> Result + where + Self: Sized, + { + let rt = Self::try_new(options)?; + Ok(rt) + } + fn rt_mut(&mut self) -> &mut JsRuntime { + self + } +} + +/// Represents a function that can be registered with the runtime +pub trait RsFunction: + Fn(&[serde_json::Value]) -> Result + 'static +{ +} +impl RsFunction for F where + F: Fn(&[serde_json::Value]) -> Result + 'static +{ +} + +/// Represents an async function that can be registered with the runtime +pub trait RsAsyncFunction: + Fn( + Vec, + ) -> Pin>>> + + 'static +{ +} +impl RsAsyncFunction for F where + F: Fn( + Vec, + ) -> Pin>>> + + 'static +{ +} + +/// Decodes a set of arguments into a vector of v8 global values +/// This is used for testing argument serialization/deserialization +#[cfg(test)] +fn decode_args_to_globals( + args: &impl serde::ser::Serialize, + runtime: &mut JsRuntime, +) -> Result>, Error> { + // Serialize to JSON string, then parse in v8 + let json = deno_core::serde_json::to_string(args)?; + + // Use execute_script to parse the JSON and get the result + let script = format!( + "JSON.parse('{}')", + json.replace('\\', "\\\\").replace('\'', "\\'") + ); + let args = runtime.execute_script("", script)?; + + let context = runtime.main_context(); + let isolate = runtime.v8_isolate(); + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = pinned.init(); + let context_local = v8::Local::new(&scope, context); + let context_scope = v8::ContextScope::new(&mut scope, context_local); + let args_local = v8::Local::new(&context_scope, &args); + + // Check if it's an array + if let Ok(arr) = v8::Local::::try_from(args_local) { + let len = arr.length(); + let mut result = Vec::with_capacity(len as usize); + for i in 0..len { + let index = v8::Integer::new(&context_scope, i as i32); + if let Some(arg) = arr.get(&context_scope, index.into()) { + let isolate: &v8::Isolate = &context_scope; + result.push(v8::Global::new(isolate, arg)); + } + } + Ok(result) + } else if args_local.is_undefined() || args_local.is_null() { + Ok(vec![]) + } else { + Ok(vec![args]) + } +} + +/// Represents the set of options accepted by the runtime constructor +pub struct RuntimeOptions { + /// A set of `deno_core` extensions to add to the runtime + pub extensions: Vec, + + /// Additional options for the built-in extensions + pub extension_options: ext::ExtensionOptions, + + /// Function to use as entrypoint if the module does not provide one + pub default_entrypoint: Option, + + /// Amount of time to run for before killing the thread + pub timeout: Duration, + + /// Optional maximum heap size for the runtime + /// + /// If the heap size is exceeded, the runtime will return a `HeapExhausted` error. + /// + /// **WARNING** this is not a minimum heap size; the underlying V8 isolate will still crash if this number is too small for startup + /// (~5mb with default features) + pub max_heap_size: Option, + + /// Optional cache provider for the module loader + #[allow(deprecated)] + pub module_cache: Option>, + + /// Optional import provider for the module loader + pub import_provider: Option>, + + /// Optional snapshot to load into the runtime + /// + /// This will reduce load times, but requires the same extensions to be loaded as when the snapshot was created + /// + /// WARNING: Snapshots MUST be used on the same system they were created on + pub startup_snapshot: Option<&'static [u8]>, + + /// Optional configuration parameters for building the underlying v8 isolate + /// + /// This can be used to alter the behavior of the runtime. + /// + /// See the `rusty_v8` documentation for more information + pub isolate_params: Option, + + /// Optional shared array buffer store to use for the runtime. + /// + /// Allows data-sharing between runtimes across threads + pub shared_array_buffer_store: Option, + + /// A whitelist of custom schema prefixes that are allowed to be loaded from javascript + /// + /// By default only `http`/`https` (`url_import` crate feature), and `file` (`fs_import` crate feature) are allowed + pub schema_whlist: HashSet, +} + +impl Default for RuntimeOptions { + fn default() -> Self { + Self { + extensions: Vec::default(), + default_entrypoint: None, + timeout: Duration::MAX, + max_heap_size: None, + module_cache: None, + import_provider: None, + startup_snapshot: None, + isolate_params: None, + shared_array_buffer_store: None, + schema_whlist: HashSet::default(), + + extension_options: ExtensionOptions::default(), + } + } +} + +/// Deno `JsRuntime` wrapper providing helper functions needed +/// by the public-facing Runtime API +/// +/// This struct is not intended to be used directly by the end user +/// It provides a set of async functions that can be used to interact with the +/// underlying deno runtime instance +pub struct InnerRuntime { + pub module_loader: Rc, + pub deno_runtime: RT, + + pub cwd: PathBuf, + pub default_entrypoint: Option, +} +impl InnerRuntime { + pub fn new( + options: RuntimeOptions, + heap_exhausted_token: CancellationToken, + ) -> Result { + let cwd = std::env::current_dir()?; + let module_loader = Rc::new(RustyLoader::new(LoaderOptions { + cache_provider: options.module_cache, + import_provider: options.import_provider, + schema_whlist: options.schema_whlist, + cwd: cwd.clone(), + + #[cfg(feature = "node_experimental")] + node_resolver: options.extension_options.node_resolver.clone(), + + ..Default::default() + })); + + // If a snapshot is provided, do not reload ESM for extensions + let is_snapshot = options.startup_snapshot.is_some(); + let extensions = ext::all_extensions( + options.extensions, + options.extension_options, + options.shared_array_buffer_store.clone(), + is_snapshot, + ); + + // If a heap size is provided, set the isolate params (preserving any user-provided params otherwise) + let isolate_params = match options.isolate_params { + Some(params) => { + if let Some(max_heap_size) = options.max_heap_size { + Some(params.heap_limits(0, max_heap_size)) + } else { + Some(params) + } + } + None => { + if let Some(max_heap_size) = options.max_heap_size { + let params = v8::Isolate::create_params().heap_limits(0, max_heap_size); + Some(params) + } else { + None + } + } + }; + + let mut deno_runtime = RT::try_new(deno_core::RuntimeOptions { + module_loader: Some(module_loader.clone()), + + extension_transpiler: Some(module_loader.as_extension_transpiler()), + create_params: isolate_params, + shared_array_buffer_store: options.shared_array_buffer_store.clone(), + + startup_snapshot: options.startup_snapshot, + extensions, + + ..Default::default() + })?; + + deno_runtime + .rt_mut() + .v8_isolate() + .set_prepare_stack_trace_callback(safe_prepare_stack_trace_callback); + + let mut feature_checker = FeatureChecker::default(); + feature_checker.set_exit_cb(Box::new(|_, _| {})); + deno_runtime + .rt_mut() + .op_state() + .borrow_mut() + .put(Arc::new(feature_checker)); + + // Add a callback to terminate the runtime if the max_heap_size limit is approached + if options.max_heap_size.is_some() { + let isolate_handle = deno_runtime.rt_mut().v8_isolate().thread_safe_handle(); + + deno_runtime + .rt_mut() + .add_near_heap_limit_callback(move |current_value, _| { + isolate_handle.terminate_execution(); + + // Signal the outer runtime to cancel block_on future (avoid hanging) and return friendly error + heap_exhausted_token.cancel(); + + // Spike the heap limit while terminating to avoid segfaulting + // Callback may fire multiple times if memory usage increases quicker then termination finalizes + 5 * current_value + }); + } + + let default_entrypoint = options.default_entrypoint; + Ok(Self { + module_loader, + deno_runtime, + cwd, + default_entrypoint, + }) + } + + /// Destroy the `RustyScript` runtime, returning the deno RT instance + #[allow(dead_code)] + pub fn into_inner(self) -> RT { + self.deno_runtime + } + + /// Access the underlying deno runtime instance directly + pub fn deno_runtime(&mut self) -> &mut JsRuntime { + self.deno_runtime.rt_mut() + } + + /// Set the current working directory for the runtime + /// This is used to resolve relative paths in the module loader + pub fn set_current_dir(&mut self, path: impl AsRef) -> Result<&Path, Error> { + let path = path.as_ref(); + let path = utilities::resolve_path(path, Some(&self.cwd))? + .to_file_path() + .map_err(|()| Error::Runtime("Invalid path".to_string()))?; + + self.cwd = path; + self.module_loader.set_current_dir(self.cwd.clone()); + Ok(&self.cwd) + } + + pub fn current_dir(&self) -> &Path { + &self.cwd + } + + /// Remove and return a value from the state + pub fn take(&mut self) -> Option + where + T: 'static, + { + let state = self.deno_runtime().op_state(); + if let Ok(mut state) = state.try_borrow_mut() { + if state.has::() { + return Some(state.take()); + } + } + + None + } + + /// Add a value to the state + /// Only one value of each type is stored + pub fn put(&mut self, value: T) -> Result<(), Error> + where + T: 'static, + { + let state = self.deno_runtime().op_state(); + let mut state = state.try_borrow_mut()?; + state.put(value); + + Ok(()) + } + + /// Register an async rust function + /// The function must return a Future that resolves to a `serde_json::Value` + /// and accept a vec of `serde_json::Value` as arguments + pub fn register_async_function(&mut self, name: &str, callback: F) -> Result<(), Error> + where + F: RsAsyncFunction, + { + let state = self.deno_runtime().op_state(); + let mut state = state.try_borrow_mut()?; + + if !state.has::>>() { + state.put(HashMap::>::new()); + } + + // Insert the callback into the state + state + .borrow_mut::>>() + .insert(name.to_string(), Box::new(callback)); + + Ok(()) + } + + /// Register a rust function + /// The function must return a `serde_json::Value` + /// and accept a slice of `serde_json::Value` as arguments + pub fn register_function(&mut self, name: &str, callback: F) -> Result<(), Error> + where + F: RsFunction, + { + let state = self.deno_runtime().op_state(); + let mut state = state.try_borrow_mut()?; + + if !state.has::>>() { + state.put(HashMap::>::new()); + } + + // Insert the callback into the state + state + .borrow_mut::>>() + .insert(name.to_string(), Box::new(callback)); + + Ok(()) + } + + /// Runs the JS event loop to completion + pub async fn await_event_loop( + &mut self, + options: PollEventLoopOptions, + timeout: Option, + ) -> Result<(), Error> { + if let Some(timeout) = timeout { + Ok(tokio::select! { + r = self.deno_runtime().run_event_loop(options) => r, + () = tokio::time::sleep(timeout) => Ok(()), + }?) + } else { + Ok(self.deno_runtime().run_event_loop(options).await?) + } + } + + /// Advances the JS event loop by one tick + /// Return true if the event loop is pending + pub async fn advance_event_loop( + &mut self, + options: PollEventLoopOptions, + ) -> Result { + let result = std::future::poll_fn(|cx| { + Poll::Ready(match self.deno_runtime().poll_event_loop(cx, options) { + Poll::Ready(t) => t.map(|()| false), + Poll::Pending => Ok(true), + }) + }) + .await?; + + Ok(result) + } + + /// Evaluate a piece of non-ECMAScript-module JavaScript code + /// The expression is evaluated in the global context, so changes persist + /// + /// Async because some expressions may require a tokio runtime + /// + /// # Arguments + /// * `expr` - A string representing the JavaScript expression to evaluate + /// + /// # Returns + /// A `Result` containing the deserialized result of the expression (`T`) + /// or an error (`Error`) if the expression cannot be evaluated or if the + /// result cannot be deserialized. + #[allow(clippy::unused_async, reason = "Prevent panic on sleep calls")] + pub async fn eval(&mut self, expr: impl ToString) -> Result, Error> { + let result = self.deno_runtime().execute_script("", expr.to_string())?; + Ok(result) + } + + /// Attempt to get a value out of the global context (globalThis.name) + /// + /// # Arguments + /// * `name` - Name of the object to extract + /// + /// # Returns + /// A `Result` containing the non-null value extracted or an error (`Error`) + pub fn get_global_value(&mut self, name: &str) -> Result, Error> { + // Use execute_script to get the global value - this avoids scope lifetime issues + let result = self + .deno_runtime() + .execute_script("", name.to_string())?; + + // Check if the value is undefined + let isolate = self.deno_runtime().v8_isolate(); + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let scope = pinned.init(); + let local = v8::Local::new(&scope, &result); + if local.is_undefined() { + return Err(Error::ValueNotFound(name.to_string())); + } + + Ok(result) + } + + /// Attempt to get a value out of a module context + /// /// + /// # Arguments + /// * `module` - A handle to a loaded module + /// * `name` - Name of the object to extract + /// + /// # Returns + /// A `Result` containing the non-null value extracted or an error (`Error`) + pub fn get_module_export_value( + &mut self, + module_context: &ModuleHandle, + name: &str, + ) -> Result, Error> { + let module_namespace = self + .deno_runtime() + .get_module_namespace(module_context.id())?; + + let context = self.deno_runtime().main_context(); + let isolate = self.deno_runtime().v8_isolate(); + + // Use std::pin::pin! pattern for proper scope management + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = pinned.init(); + let context_local = v8::Local::new(&scope, context); + let context_scope = v8::ContextScope::new(&mut scope, context_local); + + let module_namespace_local = v8::Local::new(&context_scope, module_namespace); + assert!(module_namespace_local.is_module_namespace_object()); + + // Create key string directly using context_scope + let key = v8::String::new(&context_scope, name) + .ok_or_else(|| Error::V8Encoding(name.to_string()))?; + let value = module_namespace_local.get(&context_scope, key.into()); + + match value.if_defined() { + Some(v) => { + let isolate: &v8::Isolate = &context_scope; + Ok(v8::Global::::new(isolate, v)) + } + _ => Err(Error::ValueNotFound(name.to_string())), + } + } + + pub async fn resolve_with_event_loop( + &mut self, + value: v8::Global, + ) -> Result, Error> { + let future = self.deno_runtime().resolve(value); + let result = self + .deno_runtime() + .with_event_loop_future(future, PollEventLoopOptions::default()) + .await?; + Ok(result) + } + + pub fn decode_value(&mut self, value: v8::Global) -> Result + where + T: DeserializeOwned, + { + let context = self.deno_runtime().main_context(); + let isolate = self.deno_runtime().v8_isolate(); + + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = pinned.init(); + let context_local = v8::Local::new(&scope, context); + let mut context_scope = v8::ContextScope::new(&mut scope, context_local); + let result = v8::Local::::new(&context_scope, value); + Ok(from_v8(&mut context_scope, result)?) + } + + pub fn get_value_ref( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + ) -> Result, Error> { + // Try to get the value from the module context first + let result = module_context + .and_then(|module_context| self.get_module_export_value(module_context, name).ok()); + + // If it's not found, try the global context + match result { + Some(result) => Ok(result), + None => self + .get_global_value(name) + .map_err(|_| Error::ValueNotFound(name.to_string())), + } + } + + /// Retrieves a javascript function by its name from the Deno runtime's global context. + /// + /// # Arguments + /// * `module_context` - A module handle to use for context, to find exports + /// * `name` - A string representing the name of the javascript function to retrieve. + /// + /// # Returns + /// A `Result` containing a `v8::Global` if + /// the function is found, or an error (`Error`) if the function cannot be found or + /// if it is not a valid javascript function. + pub fn get_function_by_name( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + ) -> Result, Error> { + // Get the value + let value = self.get_value_ref(module_context, name)?; + + // Convert it into a function + let isolate = self.deno_runtime().v8_isolate(); + + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let scope = pinned.init(); + let local_value = v8::Local::::new(&scope, value); + let f: v8::Local = local_value + .try_into() + .or::(Err(Error::ValueNotCallable(name.to_string())))?; + + // Return it as a global + let isolate: &v8::Isolate = &scope; + Ok(v8::Global::::new(isolate, f)) + } + + pub fn call_function_by_ref( + &mut self, + module_context: Option<&ModuleHandle>, + function: &v8::Global, + args: &impl serde::ser::Serialize, + ) -> Result, Error> { + // Get the receiver (namespace object or undefined) + let receiver = if let Some(module_context) = module_context { + Some( + self.deno_runtime() + .get_module_namespace(module_context.id())?, + ) + } else { + None + }; + + // Get the main context and create scopes + let context = self.deno_runtime().main_context(); + let isolate = self.deno_runtime().v8_isolate(); + + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = pinned.init(); + let context_local = v8::Local::new(&scope, &context); + let context_scope = v8::ContextScope::new(&mut scope, context_local); + + // Create local handle for the function + let function_local = v8::Local::new(&context_scope, function); + + // Create the receiver (undefined or module namespace) + let recv: v8::Local = match &receiver { + Some(ns) => v8::Local::new(&context_scope, ns).into(), + None => v8::undefined(&context_scope).into(), + }; + + // Serialize arguments to JSON and parse in JS + // Args should serialize to an array (tuples become arrays in serde_json) + let json_str = deno_core::serde_json::to_string(args)?; + + // Handle special case of empty tuple () which serializes to "null" + let args_locals: Vec> = if json_str == "null" || json_str == "[]" { + vec![] + } else { + let escaped = json_str.replace('\\', "\\\\").replace('\'', "\\'"); + let script_source = format!("JSON.parse('{escaped}')"); + let source = v8::String::new(&context_scope, &script_source) + .ok_or_else(|| Error::V8Encoding("Failed to create script source".to_string()))?; + let script = v8::Script::compile(&context_scope, source, None) + .ok_or_else(|| Error::Runtime("Failed to compile JSON parse script".to_string()))?; + let args_value = script + .run(&context_scope) + .ok_or_else(|| Error::Runtime("Failed to parse arguments".to_string()))?; + + // Check if it's an array + if args_value.is_array() { + let args_array: v8::Local = args_value.try_into().unwrap(); + let mut locals = Vec::with_capacity(args_array.length() as usize); + for i in 0..args_array.length() { + if let Some(arg) = args_array.get_index(&context_scope, i) { + locals.push(arg); + } + } + locals + } else { + // Single argument + vec![args_value] + } + }; + + // Call the function + let result = function_local.call(&context_scope, recv, &args_locals); + + // Handle the result + match result { + Some(value) => { + let isolate: &v8::Isolate = &context_scope; + Ok(v8::Global::new(isolate, value)) + } + None => { + // Function call failed (likely an exception was thrown) + Err(Error::Runtime("Function call failed".to_string())) + } + } + } + + /// A utility function that run provided future concurrently with the event loop. + /// + /// If the event loop resolves while polling the future, it will continue to be polled, + /// Unless it returned an error + /// + /// Useful for interacting with local inspector session. + pub async fn with_event_loop_future<'fut, T, E>( + &mut self, + mut fut: impl std::future::Future> + Unpin + 'fut, + poll_options: PollEventLoopOptions, + ) -> Result + where + deno_core::error::AnyError: From, + Error: std::convert::From, + { + // Manually implement tokio::select + std::future::poll_fn(|cx| { + let evt_status = self.deno_runtime().poll_event_loop(cx, poll_options); + let fut_status = fut.poll_unpin(cx); + + match (evt_status, fut_status) { + (Poll::Ready(Err(e)), _) => { + // Event loop failed + Poll::Ready(Err(e.into())) + } + + (_, Poll::Pending) => { + // Continue polling + Poll::Pending + } + + (_, Poll::Ready(t)) => { + for _ in 0..100 { + if let Poll::Ready(Err(e)) = + self.deno_runtime().poll_event_loop(cx, poll_options) + { + return Poll::Ready(Err(e.into())); + } + } + + // Future resolved + Poll::Ready(t.map_err(Into::into)) + } + } + }) + .await + } + + /// Get the entrypoint function for a module + pub fn get_module_entrypoint( + &mut self, + module_context: &mut ModuleHandle, + ) -> Result>, Error> { + let default = self.default_entrypoint.clone(); + + // Try to get an entrypoint from a call to `rustyscript.register_entrypoint` first + let state = self.deno_runtime().op_state(); + let mut deep_state = state.try_borrow_mut()?; + let entrypoint = deep_state.try_take::>(); + if let Some(entrypoint) = entrypoint { + return Ok(Some(entrypoint)); + } + + // Try to get an entrypoint from the default export next + if let Ok(default_export) = self.get_module_export_value(module_context, "default") { + let isolate = self.deno_runtime().v8_isolate(); + + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let scope = pinned.init(); + let default_export = v8::Local::new(&scope, default_export); + if default_export.is_function() { + if let Ok(f) = v8::Local::::try_from(default_export) { + let isolate: &v8::Isolate = &scope; + return Ok(Some(v8::Global::new(isolate, f))); + } + } + } + + // Try to get an entrypoint from the default entrypoint + if let Some(default) = default.as_deref() { + if let Ok(f) = self.get_function_by_name(Some(module_context), default) { + return Ok(Some(f)); + } + } + + Ok(None) + } + + /// Load one or more modules + /// Returns a future that resolves to a handle to the main module, or the last + /// side-module + /// + /// Will return a handle to the main module, or the last + /// side-module + pub async fn load_modules( + &mut self, + main_module: Option<&Module>, + side_modules: Vec<&Module>, + ) -> Result { + if main_module.is_none() && side_modules.is_empty() { + return Err(Error::Runtime( + "Internal error: attempt to load no modules".to_string(), + )); + } + + let mut module_handle_stub = ModuleHandle::default(); + + // Get additional modules first + for side_module in side_modules { + let module_specifier = side_module.filename().to_module_specifier(&self.cwd)?; + self.module_loader.whitelist_add(&module_specifier); + let (code, sourcemap) = transpile(&module_specifier, side_module.contents())?; + + // Now CJS translation, for node + #[cfg(feature = "node_experimental")] + let code = self + .module_loader + .translate_cjs(&module_specifier, &code) + .await?; + + let fast_code = deno_core::FastString::from(code.clone()); + + let s_modid = self + .deno_runtime() + .load_side_es_module_from_code(&module_specifier, fast_code) + .await?; + + // Update source map cache + self.module_loader.insert_source_map( + module_specifier.as_str(), + code, + sourcemap.map(|s| s.to_vec()), + ); + + let mod_load = self.deno_runtime().mod_evaluate(s_modid); + self.with_event_loop_future(mod_load, PollEventLoopOptions::default()) + .await?; + module_handle_stub = ModuleHandle::new(side_module, s_modid, None); + } + + // Load main module + if let Some(module) = main_module { + let module_specifier = module.filename().to_module_specifier(&self.cwd)?; + self.module_loader.whitelist_add(&module_specifier); + let (code, sourcemap) = transpile(&module_specifier, module.contents())?; + + // Now CJS translation, for node + #[cfg(feature = "node_experimental")] + let code = self + .module_loader + .translate_cjs(&module_specifier, &code) + .await?; + + let fast_code = deno_core::FastString::from(code.clone()); + + let module_id = self + .deno_runtime() + .load_main_es_module_from_code(&module_specifier, fast_code) + .await?; + + // Update source map cache + self.module_loader.insert_source_map( + module_specifier.as_str(), + code, + sourcemap.map(|s| s.to_vec()), + ); + + // Finish execution + let mod_load = self.deno_runtime().mod_evaluate(module_id); + self.with_event_loop_future(mod_load, PollEventLoopOptions::default()) + .await?; + module_handle_stub = ModuleHandle::new(module, module_id, None); + } + + // Try to get the default entrypoint + let entrypoint = self.get_module_entrypoint(&mut module_handle_stub)?; + + Ok(ModuleHandle::new( + module_handle_stub.module(), + module_handle_stub.id(), + entrypoint, + )) + } +} + +#[cfg(test)] +mod test_inner_runtime { + use serde::Deserialize; + + use crate::{async_callback, big_json_args, js_value::Function, json_args, sync_callback}; + + #[cfg(any(feature = "web", feature = "web_stub"))] + use crate::js_value::Promise; + + use super::*; + + /// Used for blocking functions + fn run_async_task(f: F) -> T + where + U: std::future::Future>, + F: FnOnce() -> U, + { + let timeout = Duration::from_secs(2); + let tokio = tokio::runtime::Builder::new_current_thread() + .enable_all() + .thread_keep_alive(timeout) + .build() + .unwrap(); + tokio + .block_on(async move { + tokio::time::timeout(timeout, f()) + .await + .expect("Test failed") + }) + .expect("Timed out") + } + + macro_rules! assert_v8 { + ($l:expr, $r:expr, $t:ty, $rt:expr) => { + assert_eq!($rt.decode_value::<$t>($l).expect("Wrong type"), $r,) + }; + } + + #[test] + fn test_decode_args() { + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + // empty + let args = decode_args_to_globals(&json_args!(), &mut runtime.deno_runtime) + .expect("Could not decode args"); + assert_eq!(args.len(), 0); + + // single + let args = decode_args_to_globals(&json_args!(2), &mut runtime.deno_runtime) + .expect("Could not decode args"); + assert_eq!(args.len(), 1); + + // single raw + let args = + decode_args_to_globals(&2, &mut runtime.deno_runtime).expect("Could not decode args"); + assert_eq!(args.len(), 1); + + // multiple heterogeneous + let args = decode_args_to_globals(&json_args!(2, "test"), &mut runtime.deno_runtime) + .expect("Could not decode args"); + assert_eq!(args.len(), 2); + + // multiple homogeneous + let args = decode_args_to_globals(&json_args!(2, 3), &mut runtime.deno_runtime) + .expect("Could not decode args"); + assert_eq!(args.len(), 2); + + // 16 args + let args = decode_args_to_globals( + &(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), + &mut runtime.deno_runtime, + ) + .expect("Could not decode args"); + assert_eq!(args.len(), 16); + + // 32 args + let args = decode_args_to_globals( + &big_json_args!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15 + ), + &mut runtime.deno_runtime, + ) + .expect("Could not decode args"); + assert_eq!(args.len(), 32); + } + + #[test] + fn test_put_take() { + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + runtime.put(2usize).expect("Could not put value"); + let v = runtime.take::().expect("Could not take value"); + assert_eq!(v, 2); + } + + #[test] + fn test_register_async_function() { + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + runtime + .register_async_function( + "test", + async_callback!(|a: i64, b: i64| async move { Ok::(a + b) }), + ) + .expect("Could not register function"); + + let module = Module::new( + "test.js", + " + globalThis.v = await rustyscript.async_functions.test(2, 3); + ", + ); + + let rt = &mut runtime; + let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); + + let result = runtime + .get_value_ref(Some(&module), "v") + .expect("Could not find global"); + assert_v8!(result, 5, usize, runtime); + } + + #[test] + fn test_register_function() { + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + runtime + .register_function( + "test", + sync_callback!(|a: i64, b: i64| { Ok::(a + b) }), + ) + .expect("Could not register function"); + + run_async_task(|| async move { + let v = runtime + .eval("rustyscript.functions.test(2, 3)") + .await + .expect("failed to eval"); + assert_v8!(v, 5, usize, runtime); + Ok(()) + }); + } + + #[cfg(any(feature = "web", feature = "web_stub"))] + #[test] + fn test_eval() { + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + run_async_task(|| async move { + let v = runtime.eval("2 + 2").await.expect("failed to eval"); + assert_v8!(v, 4, usize, runtime); + let result = runtime + .eval( + " + let sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + sleep(500).then(() => 2); + ", + ) + .await + .expect("failed to eval"); + + let result: Promise = runtime + .decode_value(result) + .expect("Could not decode promise"); + + let result: u32 = result.resolve(runtime.deno_runtime()).await?; + assert_eq!(result, 2); + Ok(()) + }); + } + + #[cfg(feature = "web_stub")] + #[test] + fn test_base64() { + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + run_async_task(|| async move { + let result = runtime.eval("btoa('foo')").await.expect("failed to eval"); + assert_v8!(result, "Zm9v", String, runtime); + + let result = runtime + .eval("atob(btoa('foo'))") + .await + .expect("failed to eval"); + assert_v8!(result, "foo", String, runtime); + + Ok(()) + }); + } + + #[test] + fn test_get_value_ref() { + let module = Module::new( + "test.js", + " + globalThis.a = 2; + export const b = 'test'; + export const fnc = null; + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); + + let v = runtime + .get_value_ref(None, "a") + .expect("Could not find global"); + assert_v8!(v, 2, usize, runtime); + + let v = runtime + .get_value_ref(Some(&module), "a") + .expect("Could not find global"); + assert_v8!(v, 2, usize, runtime); + + let v = runtime + .get_value_ref(Some(&module), "b") + .expect("Could not find export"); + assert_v8!(v, "test", String, runtime); + + runtime + .get_value_ref(Some(&module), "c") + .expect_err("Could not detect null"); + + runtime + .get_value_ref(Some(&module), "d") + .expect_err("Could not detect undeclared"); + } + + #[test] + fn test_get_function_by_name() { + let module = Module::new( + "test.js", + " + globalThis.fna = () => {}; + export function fnb() {} + export const fnc = 2; + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); + + runtime + .get_function_by_name(Some(&module), "fna") + .expect("Did not find global"); + runtime + .get_function_by_name(Some(&module), "fnb") + .expect("Did not find export"); + runtime + .get_function_by_name(Some(&module), "fnc") + .expect_err("Did not detect non-function"); + runtime + .get_function_by_name(Some(&module), "fnd") + .expect_err("Did not detect undefined"); + } + + #[test] + fn test_call_function_by_ref() { + let module = Module::new( + "test.js", + " + globalThis.fna = (i) => i; + export function fnb() { + return 'test'; + } + export const fnc = 2; + export const fne = () => {}; + + export const will_err = () => { + throw new Error('msg'); + } + ", + ); + + run_async_task(|| async move { + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + let handle = runtime.load_modules(Some(&module), vec![]).await?; + + let f = runtime.get_function_by_name(None, "fna").unwrap(); + let result = runtime + .call_function_by_ref(Some(&handle), &f, json_args!(2)) + .expect("Could not call global"); + assert_v8!(result, 2, usize, runtime); + + let f = runtime.get_function_by_name(Some(&handle), "fnb").unwrap(); + let result = runtime + .call_function_by_ref(Some(&handle), &f, json_args!()) + .expect("Could not call export"); + assert_v8!(result, "test", String, runtime); + + let f = runtime.get_function_by_name(Some(&handle), "fne").unwrap(); + runtime + .call_function_by_ref(Some(&handle), &f, json_args!()) + .expect("Did not allow undefined return"); + + let f = runtime + .get_function_by_name(Some(&handle), "will_err") + .unwrap(); + runtime + .call_function_by_ref(Some(&handle), &f, json_args!()) + .expect_err("Did not catch error"); + + Ok(()) + }); + } + + #[test] + fn test_ts_loader() { + let module = Module::new( + "test.ts", + " + export function test(left:number, right:number): number { + return left + right; + } + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); + + let f = runtime.get_function_by_name(Some(&module), "test").unwrap(); + let rt = &mut runtime; + let result = run_async_task(|| async move { + rt.call_function_by_ref(Some(&module), &f, json_args!(2, 3)) + }); + assert_v8!(result, 5, usize, runtime); + } + + #[cfg(any(feature = "web", feature = "web_stub"))] + #[test] + fn test_toplevel_await() { + let module = Module::new( + "test.js", + " + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + await sleep(100); + export function test() { + return 2; + } + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let module = run_async_task(|| async move { + let h = rt.load_modules(Some(&module), vec![]).await; + rt.await_event_loop(PollEventLoopOptions::default(), None) + .await?; + h + }); + + let f = runtime.get_function_by_name(Some(&module), "test").unwrap(); + let rt = &mut runtime; + let result = + run_async_task( + || async move { rt.call_function_by_ref(Some(&module), &f, json_args!()) }, + ); + assert_v8!(result, 2, usize, runtime); + } + + #[cfg(any(feature = "web", feature = "web_stub"))] + #[test] + fn test_promise() { + let module = Module::new( + "test.js", + " + export const test = () => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(2); + }, 50); + }); + } + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + run_async_task(|| async move { + let module = rt.load_modules(Some(&module), vec![]).await?; + + let f = rt.get_function_by_name(Some(&module), "test").unwrap(); + let result = rt.call_function_by_ref(Some(&module), &f, json_args!())?; + + let result = rt.resolve_with_event_loop(result).await?; + assert_v8!(result, 2, usize, rt); + + Ok(()) + }); + } + + #[cfg(any(feature = "web", feature = "web_stub"))] + #[test] + fn test_async_fn() { + let module = Module::new( + "test.js", + " + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + export async function test() { + await sleep(100); + return 2; + } + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + run_async_task(|| async move { + let module = rt.load_modules(Some(&module), vec![]).await?; + + let f = rt.get_function_by_name(Some(&module), "test")?; + let result = rt.call_function_by_ref(Some(&module), &f, json_args!())?; + let result: Promise = rt.decode_value(result).expect("Could not deserialize"); + let result: usize = result.resolve(rt.deno_runtime()).await?; + assert_eq!(2, result); + + Ok(()) + }); + } + + #[test] + fn test_deep_error() { + let module = Module::new( + "test.js", + " + await new Promise(r => setTimeout(r)); throw 'huh'; + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + run_async_task(|| async move { + let result = rt.load_modules(Some(&module), vec![]).await; + assert!(result.is_err()); + Ok(()) + }); + } + + #[test] + fn test_serialize_deep_fn() { + let module = Module::new( + "test.js", + " + let a = 2; + export const test = { + 'name': 'test', + 'func': (x) => x + a + } + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); + + #[derive(Deserialize)] + #[allow(clippy::items_after_statements)] + struct TestStruct { + #[allow(dead_code)] + name: String, + func: Function, + } + + let structure = runtime.get_value_ref(Some(&module), "test").unwrap(); + let structure: TestStruct = runtime + .decode_value(structure) + .expect("Could not deserialize"); + + let isolate = runtime.deno_runtime().v8_isolate(); + let function = structure.func.as_global(isolate); + + run_async_task(|| async move { + let value = runtime + .call_function_by_ref(Some(&module), &function, json_args!(2)) + .expect("could not call function"); + assert_v8!(value, 4, usize, runtime); + + let value = runtime + .call_function_by_ref(Some(&module), &function, json_args!(3)) + .expect("could not call function twice"); + assert_v8!(value, 5, usize, runtime); + + Ok(()) + }); + } + + #[test] + fn test_async_load_errors() { + let module = Module::new( + "test.js", + " + throw new Error('msg'); + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let module_ = module.clone(); + let result = + run_async_task( + || async move { Ok(rt.load_modules(Some(&module_), vec![]).await.is_err()) }, + ); + assert!(result); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let result = + run_async_task( + || async move { Ok(rt.load_modules(None, vec![&module]).await.is_err()) }, + ); + assert!(result); + } + + #[test] + fn test_serialize_fn() { + let module = Module::new( + "test.js", + " + export const test = (x) => 2*x; + ", + ); + + let mut runtime = + InnerRuntime::::new(RuntimeOptions::default(), CancellationToken::new()) + .expect("Could not load runtime"); + + let rt = &mut runtime; + let module = run_async_task(|| async move { rt.load_modules(Some(&module), vec![]).await }); + + let function = runtime + .get_function_by_name(Some(&module), "test") + .expect("Could not get function"); + + run_async_task(|| async move { + let value = runtime + .call_function_by_ref(Some(&module), &function, json_args!(2)) + .expect("could not call function"); + assert_v8!(value, 4, usize, runtime); + + let value = runtime + .call_function_by_ref(None, &function, json_args!(2)) + .expect("could not call function"); + assert_v8!(value, 4, usize, runtime); + + Ok(()) + }); + } +} diff --git a/src/js_value.rs b/src/js_value.rs index e39f261..38001f0 100644 --- a/src/js_value.rs +++ b/src/js_value.rs @@ -1,269 +1,298 @@ -//! This module provides a way to store and use javascript values, functions, and promises -//! The are a deserialized version of the `v8::Value` -//! -//! [Function] and [Promise] are both specializations of [Value] providing deserialize-time type checking -//! and additional utility functions for interacting with the runtime -use deno_core::{ - serde_v8::GlobalValue, - v8::{self, HandleScope}, -}; -use serde::Deserialize; - -/// A macro to implement the common functions for [Function], [Promise], and [Value] -macro_rules! impl_v8 { - ($name:ident$(<$generic:ident>)?, $checker:ident $(,)?) => { - impl $(<$generic>)? $name $(<$generic>)? where - $( $generic: serde::de::DeserializeOwned, )? { - /// Consume this struct and return the underlying `V8Value` - #[allow(dead_code)] - pub(crate) fn into_inner(self) -> V8Value<$checker> { - self.0 - } - - /// Returns the underlying [`crate::deno_core::v8::Global`] - /// This is useful if you want to pass the value to a [`crate::deno_core::JsRuntime`] function directly - #[must_use] - pub fn into_v8(self) -> v8::Global { - self.0 .0 - } - - /// Returns a reference to the underlying [`crate::deno_core::v8::Global`] - /// This is useful if you want to pass the value to a [`crate::deno_core::JsRuntime`] function directly - #[must_use] - pub fn as_v8(&self) -> &v8::Global { - &self.0 .0 - } - - /// Creates a new instance of this struct from a global value - /// - /// # Errors - /// Will return an error if the value is the wrong type - /// For `Value`, this check cannot fail - pub fn try_from_v8<'a, H>( - scope: &mut v8::HandleScope<'a>, - value: v8::Global, - ) -> Result - where - v8::Local<'a, v8::Value>: From>, - { - let local: v8::Local = v8::Local::new(scope, value).into(); - v8::Global::new(scope, local).try_into() - } - - /// Creates a new instance of this struct from a global value - /// Makes no attempt to check the type of the value - /// This can result in a panic if the value is not of the correct type - /// - /// # Safety - /// This function is unsafe because it does not check the type of the value - /// If the value is not of the correct type, a panic will occur - /// It is recommended to use [`Self::try_from_v8`] instead - #[must_use] - pub unsafe fn from_v8_unchecked(value: v8::Global) -> Self { - let inner = V8Value::<$checker>(value, std::marker::PhantomData); - Self(inner $(, std::marker::PhantomData::<$generic>)?) - } - } - impl<'de$(, $generic)?> serde::Deserialize<'de> for $name $(<$generic>)? - $(where $generic: serde::de::DeserializeOwned,)? - { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let inner = V8Value::<$checker>::deserialize(deserializer)?; - Ok(Self(inner $(, std::marker::PhantomData::<$generic>)?)) - } - } - - #[allow(clippy::from_over_into)] - impl $(<$generic>)? Into> for $name $(<$generic>)? $(where $generic: serde::de::DeserializeOwned)? { - fn into(self) -> v8::Global { - self.0 .0 - } - } - - impl $(<$generic>)? TryFrom> for $name $(<$generic>)? $(where $generic: serde::de::DeserializeOwned)? { - type Error = crate::Error; - fn try_from(value: v8::Global) -> Result { - <$checker as $crate::js_value::V8TypeChecker>::validate(value.clone())?; - let inner = V8Value::<$checker>(value, std::marker::PhantomData); - Ok(Self(inner $(, std::marker::PhantomData::<$generic>)?)) - } - } - }; -} - -/// A macro to implement type checkers for [Function], [Promise], and [Value] -macro_rules! impl_checker { - ($name:ident, $v8_name:ident, $checker_fn:ident, |$err_ty:ident| $err:block) => { - #[doc = "Implementations of `V8TypeChecker`"] - #[doc = concat!("Guards for `v8::", stringify!($v8_name), "` values")] - #[derive(Eq, Hash, PartialEq, Debug, Clone, Deserialize)] - pub(crate) struct $name; - impl $crate::js_value::V8TypeChecker for $name { - type Output = v8::$v8_name; - fn validate(value: v8::Global) -> Result<(), crate::Error> { - let raw: &v8::Value = unsafe { v8::Handle::get_unchecked(&value) }; - if raw.$checker_fn() { - Ok(()) - } else { - let $err_ty = raw.type_repr().to_string(); - Err($err) - } - } - } - }; - - ($name:ident, $v8_name:ident) => { - #[doc = "Implementation of `V8TypeChecker`"] - #[doc = concat!("Guards for `v8::", stringify!($v8_name), "` values")] - #[derive(Eq, Hash, PartialEq, Debug, Clone, Deserialize)] - pub(crate) struct $name; - impl V8TypeChecker for $name { - type Output = v8::$v8_name; - fn validate(_: v8::Global) -> Result<(), crate::Error> { - Ok(()) - } - } - }; -} - -/// A trait that is used to check if a `v8::Value` is of a certain type -/// Will cause a panic if validate is insufficient to verify that the -/// given value is of type `T::Output` -pub(crate) trait V8TypeChecker { - /// The v8 type that this checker guards for - type Output; - - /// Checks if a `v8::Value` is of the output type - /// If the value is not of the output type, an error is returned - /// - /// Note: If the guard is not sufficient to verify the type, a panic will occur - /// when this checker is used - fn validate(value: v8::Global) -> Result<(), crate::Error>; -} - -// For values -impl_checker!(DefaultTypeChecker, Value); - -/// The core struct behind the [Function], [Promise], and [Value] types -/// Should probably not be user-facing -/// TODO: Safer API for this so we can make it public eventually -/// -/// A Deserializable javascript object, that can be stored and used later -/// Must live as long as the runtime it was birthed from -#[derive(Eq, Hash, PartialEq, Debug, Clone)] -pub(crate) struct V8Value( - v8::Global, - std::marker::PhantomData, -); - -impl V8Value { - /// Returns the underlying global as a local in the type configured by the type checker - pub(crate) fn as_local<'a>(&self, scope: &mut HandleScope<'a>) -> v8::Local<'a, T::Output> - where - v8::Local<'a, T::Output>: TryFrom>, - { - let local = v8::Local::new(scope, &self.0); - v8::Local::<'a, T::Output>::try_from(local) - .ok() - .expect("Failed to convert V8Value: Invalid V8TypeChecker!") - } - - /// Returns the underlying global in the type configured by the type checker - pub(crate) fn as_global<'a>(&self, scope: &mut HandleScope<'a>) -> v8::Global - where - v8::Local<'a, T::Output>: TryFrom>, - { - let local = self.as_local(scope); - v8::Global::new(scope, local) - } -} - -impl<'de, T: V8TypeChecker> serde::Deserialize<'de> for V8Value { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = GlobalValue::deserialize(deserializer)?; - T::validate(value.v8_value.clone()).map_err(serde::de::Error::custom)?; - Ok(Self(value.v8_value, std::marker::PhantomData)) - } -} - -/// A Deserializable javascript value, that can be stored and used later -/// Can only be used on the same runtime it was created on -/// -/// This mimics the auto-decoding that happens when providing a type parameter to Runtime functions -#[derive(Eq, Hash, PartialEq, Debug, Clone)] -pub struct Value(V8Value); -impl_v8!(Value, DefaultTypeChecker); -impl Value { - /// Converts the value to an arbitrary rust type - /// Mimics the auto-decoding using `from_v8` that normally happens - /// Note: This will not await the event loop, or resolve promises - /// Use [`crate::js_value::Promise`] as the generic T for that - /// - /// # Errors - /// Will return an error if the value cannot be deserialized into the given type - pub fn try_into(self, runtime: &mut crate::Runtime) -> Result - where - T: serde::de::DeserializeOwned, - { - let mut scope = runtime.deno_runtime().handle_scope(); - let local = self.0.as_local(&mut scope); - Ok(deno_core::serde_v8::from_v8(&mut scope, local)?) - } - - /// Contructs a new Value from a `v8::Value` global - #[must_use] - pub fn from_v8(value: v8::Global) -> Self { - Self(V8Value(value, std::marker::PhantomData)) - } -} - -mod function; -pub use function::*; - -mod promise; -pub use promise::*; - -mod string; -pub use string::*; - -mod map; -pub use map::*; - -#[cfg(test)] -mod test { - use super::*; - use crate::{Module, Runtime, RuntimeOptions}; - - #[test] - fn test_value() { - let module = Module::new( - "test.js", - " - export const f = 42; - export const g = () => 42; - ", - ); - - let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); - let handle = runtime.load_module(&module).unwrap(); - - let f: Value = runtime.get_value(Some(&handle), "f").unwrap(); - let value: usize = f.try_into(&mut runtime).unwrap(); - assert_eq!(value, 42); - - let g: Value = runtime.get_value(Some(&handle), "g").unwrap(); - let global = g.into_v8(); - let _f = Function::try_from_v8(&mut runtime.deno_runtime().handle_scope(), global.clone()) - .unwrap(); - let f = unsafe { Function::from_v8_unchecked(global) }; - let _f = f - .into_inner() - .as_local(&mut runtime.deno_runtime().handle_scope()); - } -} +//! This module provides a way to store and use javascript values, functions, and promises +//! The are a deserialized version of the `v8::Value` +//! +//! [Function] and [Promise] are both specializations of [Value] providing deserialize-time type checking +//! and additional utility functions for interacting with the runtime +use deno_core::{serde_v8::GlobalValue, v8}; +use serde::Deserialize; + +/// A macro to implement the common functions for [Function], [Promise], and [Value] +macro_rules! impl_v8 { + ($name:ident$(<$generic:ident>)?, $checker:ident $(,)?) => { + impl $(<$generic>)? $name $(<$generic>)? where + $( $generic: serde::de::DeserializeOwned, )? { + /// Consume this struct and return the underlying `V8Value` + #[allow(dead_code)] + pub(crate) fn into_inner(self) -> V8Value<$checker> { + self.0 + } + + /// Returns the underlying [`crate::deno_core::v8::Global`] + /// This is useful if you want to pass the value to a [`crate::deno_core::JsRuntime`] function directly + #[must_use] + pub fn into_v8(self) -> v8::Global { + self.0 .0 + } + + /// Returns a reference to the underlying [`crate::deno_core::v8::Global`] + /// This is useful if you want to pass the value to a [`crate::deno_core::JsRuntime`] function directly + #[must_use] + pub fn as_v8(&self) -> &v8::Global { + &self.0 .0 + } + + /// Creates a new instance of this struct from a global value + /// + /// # Errors + /// Will return an error if the value is the wrong type + /// For `Value`, this check cannot fail + pub fn try_from_v8<'a, H>( + scope: &mut v8::PinScope<'a, '_>, + value: v8::Global, + ) -> Result + where + v8::Local<'a, v8::Value>: From>, + { + let local: v8::Local = v8::Local::new(scope, value).into(); + // Get isolate reference from the scope using Deref + let isolate: &v8::Isolate = scope; + v8::Global::new(isolate, local).try_into() + } + + /// Creates a new instance of this struct from a global value + /// Makes no attempt to check the type of the value + /// This can result in a panic if the value is not of the correct type + /// + /// # Safety + /// This function is unsafe because it does not check the type of the value + /// If the value is not of the correct type, a panic will occur + /// It is recommended to use [`Self::try_from_v8`] instead + #[must_use] + pub unsafe fn from_v8_unchecked(value: v8::Global) -> Self { + let inner = V8Value::<$checker>(value, std::marker::PhantomData); + Self(inner $(, std::marker::PhantomData::<$generic>)?) + } + } + impl<'de$(, $generic)?> serde::Deserialize<'de> for $name $(<$generic>)? + $(where $generic: serde::de::DeserializeOwned,)? + { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let inner = V8Value::<$checker>::deserialize(deserializer)?; + Ok(Self(inner $(, std::marker::PhantomData::<$generic>)?)) + } + } + + #[allow(clippy::from_over_into)] + impl $(<$generic>)? Into> for $name $(<$generic>)? $(where $generic: serde::de::DeserializeOwned)? { + fn into(self) -> v8::Global { + self.0 .0 + } + } + + impl $(<$generic>)? TryFrom> for $name $(<$generic>)? $(where $generic: serde::de::DeserializeOwned)? { + type Error = crate::Error; + fn try_from(value: v8::Global) -> Result { + <$checker as $crate::js_value::V8TypeChecker>::validate(value.clone())?; + let inner = V8Value::<$checker>(value, std::marker::PhantomData); + Ok(Self(inner $(, std::marker::PhantomData::<$generic>)?)) + } + } + }; +} + +/// A macro to implement type checkers for [Function], [Promise], and [Value] +macro_rules! impl_checker { + ($name:ident, $v8_name:ident, $checker_fn:ident, |$err_ty:ident| $err:block) => { + #[doc = "Implementations of `V8TypeChecker`"] + #[doc = concat!("Guards for `v8::", stringify!($v8_name), "` values")] + #[derive(Eq, Hash, PartialEq, Debug, Clone, Deserialize)] + pub(crate) struct $name; + impl $crate::js_value::V8TypeChecker for $name { + type Output = v8::$v8_name; + fn validate(value: v8::Global) -> Result<(), crate::Error> { + let raw: &v8::Value = unsafe { v8::Handle::get_unchecked(&value) }; + if raw.$checker_fn() { + Ok(()) + } else { + let $err_ty = raw.type_repr().to_string(); + Err($err) + } + } + } + }; + + ($name:ident, $v8_name:ident) => { + #[doc = "Implementation of `V8TypeChecker`"] + #[doc = concat!("Guards for `v8::", stringify!($v8_name), "` values")] + #[derive(Eq, Hash, PartialEq, Debug, Clone, Deserialize)] + pub(crate) struct $name; + impl V8TypeChecker for $name { + type Output = v8::$v8_name; + fn validate(_: v8::Global) -> Result<(), crate::Error> { + Ok(()) + } + } + }; +} + +/// A trait that is used to check if a `v8::Value` is of a certain type +/// Will cause a panic if validate is insufficient to verify that the +/// given value is of type `T::Output` +pub(crate) trait V8TypeChecker { + /// The v8 type that this checker guards for + type Output; + + /// Checks if a `v8::Value` is of the output type + /// If the value is not of the output type, an error is returned + /// + /// Note: If the guard is not sufficient to verify the type, a panic will occur + /// when this checker is used + fn validate(value: v8::Global) -> Result<(), crate::Error>; +} + +// For values +impl_checker!(DefaultTypeChecker, Value); + +/// The core struct behind the [Function], [Promise], and [Value] types +/// Should probably not be user-facing +/// TODO: Safer API for this so we can make it public eventually +/// +/// A Deserializable javascript object, that can be stored and used later +/// Must live as long as the runtime it was birthed from +#[derive(Eq, Hash, PartialEq, Debug, Clone)] +pub(crate) struct V8Value( + v8::Global, + std::marker::PhantomData, +); + +impl V8Value { + /// Returns the underlying global as a local in the type configured by the type checker + pub(crate) fn as_local<'a, 'i, C>( + &self, + scope: &v8::PinnedRef<'a, v8::HandleScope<'i, C>>, + ) -> v8::Local<'a, T::Output> + where + v8::Local<'a, T::Output>: TryFrom>, + { + // SAFETY: v8::Local::new requires HandleScope<'_, ()> but works with any context type. + // The context parameter is a phantom type marker and doesn't affect memory layout. + // We transmute the scope reference to the required type for the API call. + let scope_ref = unsafe { + &*std::ptr::from_ref(scope).cast::>>() + }; + let local = v8::Local::::new(scope_ref, &self.0); + v8::Local::<'a, T::Output>::try_from(local) + .ok() + .expect("Failed to convert V8Value: Invalid V8TypeChecker!") + } + + /// Returns the underlying global in the type configured by the type checker + /// Note: This performs an unchecked cast - the type checker should have validated the type + pub(crate) fn as_global(&self, _isolate: &v8::Isolate) -> v8::Global + where + T::Output: 'static, + { + // SAFETY: The type checker should have validated that self.0 is of the correct type + // We use transmute to convert v8::Global to v8::Global + // This is safe because v8::Global is just a pointer wrapper + unsafe { std::mem::transmute(self.0.clone()) } + } +} + +impl<'de, T: V8TypeChecker> serde::Deserialize<'de> for V8Value { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = GlobalValue::deserialize(deserializer)?; + T::validate(value.v8_value.clone()).map_err(serde::de::Error::custom)?; + Ok(Self(value.v8_value, std::marker::PhantomData)) + } +} + +/// A Deserializable javascript value, that can be stored and used later +/// Can only be used on the same runtime it was created on +/// +/// This mimics the auto-decoding that happens when providing a type parameter to Runtime functions +#[derive(Eq, Hash, PartialEq, Debug, Clone)] +pub struct Value(V8Value); +impl_v8!(Value, DefaultTypeChecker); +impl Value { + /// Converts the value to an arbitrary rust type + /// Mimics the auto-decoding using `from_v8` that normally happens + /// Note: This will not await the event loop, or resolve promises + /// Use [`crate::js_value::Promise`] as the generic T for that + /// + /// # Errors + /// Will return an error if the value cannot be deserialized into the given type + pub fn try_into(self, runtime: &mut crate::Runtime) -> Result + where + T: serde::de::DeserializeOwned, + { + let context = runtime.deno_runtime().main_context(); + let isolate = runtime.deno_runtime().v8_isolate(); + let scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = scope.init(); + let context_local = v8::Local::new(&scope, context); + let mut context_scope = v8::ContextScope::new(&mut scope, context_local); + let local = self.0.as_local(&context_scope); + Ok(deno_core::serde_v8::from_v8(&mut context_scope, local)?) + } + + /// Contructs a new Value from a `v8::Value` global + #[must_use] + pub fn from_v8(value: v8::Global) -> Self { + Self(V8Value(value, std::marker::PhantomData)) + } +} + +mod function; +pub use function::*; + +mod promise; +pub use promise::*; + +mod string; +pub use string::*; + +mod map; +pub use map::*; + +#[cfg(test)] +mod test { + use super::*; + use crate::{Module, Runtime, RuntimeOptions}; + + #[test] + fn test_value() { + let module = Module::new( + "test.js", + " + export const f = 42; + export const g = () => 42; + ", + ); + + let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); + let handle = runtime.load_module(&module).unwrap(); + + // Test basic value extraction + let f: Value = runtime.get_value(Some(&handle), "f").unwrap(); + let value: usize = f.try_into(&mut runtime).unwrap(); + assert_eq!(value, 42); + + // Test function extraction and conversion + let g: Value = runtime.get_value(Some(&handle), "g").unwrap(); + let global = g.into_v8(); + + // Test try_from_v8 with proper context scope + let context = runtime.deno_runtime().main_context(); + let isolate = runtime.deno_runtime().v8_isolate(); + let pinned = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = pinned.init(); + let context_local = v8::Local::new(&scope, context); + let mut context_scope = v8::ContextScope::new(&mut scope, context_local); + + // Test that we can create a Function from the global + let _f = Function::try_from_v8(&mut context_scope, global.clone()).unwrap(); + + // Test from_v8_unchecked + let f = unsafe { Function::from_v8_unchecked(global.clone()) }; + + // Test as_local with context scope + let _local = f.into_inner().as_local(&context_scope); + } +} diff --git a/src/js_value/function.rs b/src/js_value/function.rs index c1d4f16..1ae496e 100644 --- a/src/js_value/function.rs +++ b/src/js_value/function.rs @@ -1,4 +1,4 @@ -use deno_core::v8::{self, HandleScope}; +use deno_core::v8; use serde::Deserialize; use super::V8Value; @@ -13,8 +13,8 @@ impl_checker!(FunctionTypeChecker, Function, is_function, |e| { }); impl Function { - pub(crate) fn as_global(&self, scope: &mut HandleScope<'_>) -> v8::Global { - self.0.as_global(scope) + pub(crate) fn as_global(&self, isolate: &v8::Isolate) -> v8::Global { + self.0.as_global(isolate) } /// Returns true if the function is async diff --git a/src/js_value/map.rs b/src/js_value/map.rs index f047393..c9a1297 100644 --- a/src/js_value/map.rs +++ b/src/js_value/map.rs @@ -1,136 +1,161 @@ -use deno_core::v8::{self, GetPropertyNamesArgs, HandleScope}; -use serde::Deserialize; - -use super::V8Value; - -/// A Deserializable javascript object, that can be stored and used later -/// Must live as long as the runtime it was birthed from -/// -/// Allows read-only access properties of the object, and convert it to a hashmap -/// (skipping any keys that are not valid UTF-8) -/// -/// [`Map::get`] returns a [`crate::js_value::Value`] which can be converted to any rust type, including promises or functions -#[derive(Eq, Hash, PartialEq, Debug, Clone)] -pub struct Map(V8Value); -impl_v8!(Map, ObjectTypeChecker); -impl_checker!(ObjectTypeChecker, Object, is_object, |e| { - crate::Error::JsonDecode(format!("Expected an object, found `{e}`")) -}); - -impl Map { - /// Gets a value from the map - /// Warning: If a key is not valid UTF-8, the value may be inaccessible - pub fn get(&self, key: &str, runtime: &mut crate::Runtime) -> Option { - let mut scope = runtime.deno_runtime().handle_scope(); - self.get_property_by_name(&mut scope, key) - } - - /// Converts the map to a hashmap - /// Skips any keys that are not valid UTF-8 - pub fn to_hashmap( - &self, - runtime: &mut crate::Runtime, - ) -> std::collections::HashMap { - let mut scope = runtime.deno_runtime().handle_scope(); - self.to_rust_hashmap(&mut scope) - } - - /// Returns the keys of the map - /// Warning: If a key is not valid UTF-8, the value may be inaccessible - pub fn keys(&self, runtime: &mut crate::Runtime) -> Vec { - let mut scope = runtime.deno_runtime().handle_scope(); - self.get_string_keys(&mut scope) - } - - /// Returns the number of keys in the map - /// Skips any keys that are not valid UTF-8 - pub fn len(&self, runtime: &mut crate::Runtime) -> usize { - let mut scope = runtime.deno_runtime().handle_scope(); - self.get_string_keys(&mut scope).len() - } - - pub(crate) fn to_rust_hashmap( - &self, - scope: &mut HandleScope, - ) -> std::collections::HashMap { - let keys = self.get_string_keys(scope); - let mut map = std::collections::HashMap::new(); - - for name in keys { - match self.get_property_by_name(scope, &name) { - Some(value) => map.insert(name, value), - None => None, - }; - } - - map - } - - pub(crate) fn get_property_by_name( - &self, - scope: &mut HandleScope, - name: &str, - ) -> Option { - let local = self.0.as_local(scope); - let key = v8::String::new(scope, name).unwrap(); - let value = local.get(scope, key.into())?; - - let value = v8::Global::new(scope, value); - Some(crate::js_value::Value::from_v8(value)) - } - - pub(crate) fn get_string_keys(&self, scope: &mut HandleScope) -> Vec { - let local = self.0.as_local(scope); - let mut keys = vec![]; - - let v8_keys = local.get_own_property_names( - scope, - GetPropertyNamesArgs { - mode: v8::KeyCollectionMode::OwnOnly, - property_filter: v8::PropertyFilter::ALL_PROPERTIES, - index_filter: v8::IndexFilter::IncludeIndices, - key_conversion: v8::KeyConversionMode::ConvertToString, - }, - ); - - if let Some(v8_keys) = v8_keys { - for i in 0..v8_keys.length() { - let key = v8_keys.get_index(scope, i).unwrap(); - let key = key.to_rust_string_lossy(scope); - keys.push(key); - } - } - - keys - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{Module, Runtime, RuntimeOptions}; - - #[test] - fn test_map() { - let module = Module::new( - "test.js", - " - export const m = { a: 1, b: 2, c: 3, 0: 4 }; - ", - ); - - let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); - let handle = runtime.load_module(&module).unwrap(); - - let m: Map = runtime.get_value(Some(&handle), "m").expect("oops"); - assert_eq!(m.len(&mut runtime), 4); - - let a = m.get("a", &mut runtime).unwrap(); - let a: usize = a.try_into(&mut runtime).unwrap(); - assert_eq!(a, 1); - - let zero = m.get("0", &mut runtime).unwrap(); - let zero: usize = zero.try_into(&mut runtime).unwrap(); - assert_eq!(zero, 4); - } -} +use deno_core::v8::{self, GetPropertyNamesArgs}; +use serde::Deserialize; + +use super::V8Value; + +/// A Deserializable javascript object, that can be stored and used later +/// Must live as long as the runtime it was birthed from +/// +/// Allows read-only access properties of the object, and convert it to a hashmap +/// (skipping any keys that are not valid UTF-8) +/// +/// [`Map::get`] returns a [`crate::js_value::Value`] which can be converted to any rust type, including promises or functions +#[derive(Eq, Hash, PartialEq, Debug, Clone)] +pub struct Map(V8Value); +impl_v8!(Map, ObjectTypeChecker); +impl_checker!(ObjectTypeChecker, Object, is_object, |e| { + crate::Error::JsonDecode(format!("Expected an object, found `{e}`")) +}); + +impl Map { + /// Gets a value from the map + /// Warning: If a key is not valid UTF-8, the value may be inaccessible + pub fn get(&self, key: &str, runtime: &mut crate::Runtime) -> Option { + let deno_runtime = runtime.deno_runtime(); + let context = deno_runtime.main_context(); + let isolate = deno_runtime.v8_isolate(); + let handle_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut handle_scope = handle_scope.init(); + let context_local = v8::Local::new(&handle_scope, context); + let mut context_scope = v8::ContextScope::new(&mut handle_scope, context_local); + self.get_property_by_name_impl(&mut context_scope, key) + } + + /// Converts the map to a hashmap + /// Skips any keys that are not valid UTF-8 + pub fn to_hashmap( + &self, + runtime: &mut crate::Runtime, + ) -> std::collections::HashMap { + let deno_runtime = runtime.deno_runtime(); + let context = deno_runtime.main_context(); + let isolate = deno_runtime.v8_isolate(); + let handle_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut handle_scope = handle_scope.init(); + let context_local = v8::Local::new(&handle_scope, context); + let mut context_scope = v8::ContextScope::new(&mut handle_scope, context_local); + self.to_rust_hashmap_impl(&mut context_scope) + } + + /// Returns the keys of the map + /// Warning: If a key is not valid UTF-8, the value may be inaccessible + pub fn keys(&self, runtime: &mut crate::Runtime) -> Vec { + let deno_runtime = runtime.deno_runtime(); + let context = deno_runtime.main_context(); + let isolate = deno_runtime.v8_isolate(); + let handle_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut handle_scope = handle_scope.init(); + let context_local = v8::Local::new(&handle_scope, context); + let mut context_scope = v8::ContextScope::new(&mut handle_scope, context_local); + self.get_string_keys_impl(&mut context_scope) + } + + /// Returns the number of keys in the map + /// Skips any keys that are not valid UTF-8 + pub fn len(&self, runtime: &mut crate::Runtime) -> usize { + let deno_runtime = runtime.deno_runtime(); + let context = deno_runtime.main_context(); + let isolate = deno_runtime.v8_isolate(); + let handle_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut handle_scope = handle_scope.init(); + let context_local = v8::Local::new(&handle_scope, context); + let mut context_scope = v8::ContextScope::new(&mut handle_scope, context_local); + self.get_string_keys_impl(&mut context_scope).len() + } + + fn to_rust_hashmap_impl( + &self, + scope: &mut v8::ContextScope, + ) -> std::collections::HashMap { + let keys = self.get_string_keys_impl(scope); + let mut map = std::collections::HashMap::new(); + + for name in keys { + match self.get_property_by_name_impl(scope, &name) { + Some(value) => map.insert(name, value), + None => None, + }; + } + + map + } + + fn get_property_by_name_impl( + &self, + scope: &mut v8::ContextScope, + name: &str, + ) -> Option { + let local = self.0.as_local(scope); + let key = v8::String::new(scope, name).unwrap(); + let value = local.get(scope, key.into())?; + + let isolate: &v8::Isolate = scope; + let value = v8::Global::new(isolate, value); + Some(crate::js_value::Value::from_v8(value)) + } + + fn get_string_keys_impl(&self, scope: &mut v8::ContextScope) -> Vec { + let local = self.0.as_local(scope); + let mut keys = vec![]; + + let v8_keys = local.get_own_property_names( + scope, + GetPropertyNamesArgs { + mode: v8::KeyCollectionMode::OwnOnly, + property_filter: v8::PropertyFilter::ALL_PROPERTIES, + index_filter: v8::IndexFilter::IncludeIndices, + key_conversion: v8::KeyConversionMode::ConvertToString, + }, + ); + + if let Some(v8_keys) = v8_keys { + for i in 0..v8_keys.length() { + let key = v8_keys.get_index(scope, i).unwrap(); + let key = key.to_rust_string_lossy(scope); + keys.push(key); + } + } + + keys + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{Module, Runtime, RuntimeOptions}; + + #[test] + fn test_map() { + let module = Module::new( + "test.js", + " + export const m = { a: 1, b: 2, c: 3, 0: 4 }; + ", + ); + + let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); + let handle = runtime.load_module(&module).unwrap(); + + let m: Map = runtime.get_value(Some(&handle), "m").expect("oops"); + assert_eq!(m.len(&mut runtime), 4); + + let a = m.get("a", &mut runtime).unwrap(); + let a: usize = a.try_into(&mut runtime).unwrap(); + assert_eq!(a, 1); + + let zero = m.get("0", &mut runtime).unwrap(); + let zero: usize = zero.try_into(&mut runtime).unwrap(); + assert_eq!(zero, 4); + } +} diff --git a/src/js_value/promise.rs b/src/js_value/promise.rs index a09471b..73dee49 100644 --- a/src/js_value/promise.rs +++ b/src/js_value/promise.rs @@ -33,9 +33,14 @@ where let result = runtime .with_event_loop_future(future, PollEventLoopOptions::default()) .await?; - let mut scope = runtime.handle_scope(); - let local = v8::Local::new(&mut scope, &result); - Ok(deno_core::serde_v8::from_v8(&mut scope, local)?) + let context = runtime.main_context(); + let isolate = runtime.v8_isolate(); + let scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = scope.init(); + let context_local = v8::Local::new(&scope, context); + let mut context_scope = v8::ContextScope::new(&mut scope, context_local); + let local = v8::Local::new(&context_scope, &result); + Ok(deno_core::serde_v8::from_v8(&mut context_scope, local)?) } /// Returns a future that resolves the promise @@ -58,8 +63,13 @@ where /// Checks if the promise is pending or already resolved pub fn is_pending(&self, runtime: &mut crate::Runtime) -> bool { - let mut scope = runtime.deno_runtime().handle_scope(); - let value = self.0.as_local(&mut scope); + let context = runtime.deno_runtime().main_context(); + let isolate = runtime.deno_runtime().v8_isolate(); + let scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = scope.init(); + let context_local = v8::Local::new(&scope, context); + let context_scope = v8::ContextScope::new(&mut scope, context_local); + let value = self.0.as_local(&context_scope); value.state() == v8::PromiseState::Pending } @@ -67,19 +77,24 @@ where /// or `Poll::Ready(Ok(T))` if the promise is resolved /// or `Poll::Ready(Err(Error))` if the promise is rejected pub fn poll_promise(&self, runtime: &mut crate::Runtime) -> std::task::Poll> { - let mut scope = runtime.deno_runtime().handle_scope(); - let value = self.0.as_local(&mut scope); + let context = runtime.deno_runtime().main_context(); + let isolate = runtime.deno_runtime().v8_isolate(); + let scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = scope.init(); + let context_local = v8::Local::new(&scope, context); + let mut context_scope = v8::ContextScope::new(&mut scope, context_local); + let value = self.0.as_local(&context_scope); match value.state() { PromiseState::Pending => std::task::Poll::Pending, PromiseState::Rejected => { - let error = value.result(&mut scope); - let error = deno_core::error::JsError::from_v8_exception(&mut scope, error); + let error = value.result(&context_scope); + let error = deno_core::error::JsError::from_v8_exception(&mut context_scope, error); std::task::Poll::Ready(Err(error.into())) } PromiseState::Fulfilled => { - let result = value.result(&mut scope); - match deno_core::serde_v8::from_v8::(&mut scope, result) { + let result = value.result(&context_scope); + match deno_core::serde_v8::from_v8::(&mut context_scope, result) { Ok(value) => std::task::Poll::Ready(Ok(value)), Err(e) => std::task::Poll::Ready(Err(e.into())), } diff --git a/src/js_value/string.rs b/src/js_value/string.rs index 9958b08..cb8201a 100644 --- a/src/js_value/string.rs +++ b/src/js_value/string.rs @@ -1,102 +1,123 @@ -use deno_core::v8::{self, HandleScope, WriteFlags}; -use serde::Deserialize; - -use super::V8Value; - -/// A Deserializable javascript UTF-16 string, that can be stored and used later -/// Must live as long as the runtime it was birthed from -#[derive(Eq, Hash, PartialEq, Debug, Clone)] -pub struct String(V8Value); -impl_v8!(String, StringTypeChecker); -impl_checker!(StringTypeChecker, String, is_string, |e| { - crate::Error::JsonDecode(format!("Expected a string, found `{e}`")) -}); - -impl String { - /// Converts the string to a rust string - /// Potentially lossy, if the string contains orphan UTF-16 surrogates - pub fn to_string_lossy(&self, runtime: &mut crate::Runtime) -> std::string::String { - let mut scope = runtime.deno_runtime().handle_scope(); - self.to_rust_string_lossy(&mut scope) - } - - /// Converts the string to a rust string - /// If the string contains orphan UTF-16 surrogates, it may return None - /// In that case, you can use `to_string_lossy` to get a lossy conversion - pub fn to_string(&self, runtime: &mut crate::Runtime) -> Option { - let bytes = self.to_utf8_bytes(runtime); - std::string::String::from_utf8(bytes).ok() - } - - /// Converts the string to a UTF-8 character buffer in the form of a `Vec` - /// Excludes the null terminator - pub fn to_utf8_bytes(&self, runtime: &mut crate::Runtime) -> Vec { - let mut scope = runtime.deno_runtime().handle_scope(); - self.to_utf8_buffer(&mut scope) - } - - /// Converts the string to a UTF-16 character buffer in the form of a `Vec` - /// Excludes the null terminator - pub fn to_utf16_bytes(&self, runtime: &mut crate::Runtime) -> Vec { - let mut scope = runtime.deno_runtime().handle_scope(); - self.to_utf16_buffer(&mut scope) - } - - pub(crate) fn to_rust_string_lossy(&self, scope: &mut HandleScope<'_>) -> std::string::String { - let local = self.0.as_local(scope); - local.to_rust_string_lossy(scope) - } - - pub(crate) fn to_utf16_buffer(&self, scope: &mut HandleScope<'_>) -> Vec { - let local = self.0.as_local(scope); - let u16_len = local.length(); - let mut buffer = vec![0; u16_len]; - - local.write_v2(scope, 0, &mut buffer, WriteFlags::empty()); - buffer - } - - pub(crate) fn to_utf8_buffer(&self, scope: &mut HandleScope<'_>) -> Vec { - let local = self.0.as_local(scope); - let u8_len = local.utf8_length(scope); - let mut buffer = vec![0; u8_len]; - - local.write_utf8_v2(scope, &mut buffer, WriteFlags::empty(), None); - buffer - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{Module, Runtime, RuntimeOptions}; - - #[test] - fn test_string() { - let module = Module::new( - "test.js", - " - // Valid UTF-8 - export const good = 'Hello, World!'; - - // Invalid UTF-8, valid UTF-16 - export const bad = '\\ud83d\\ude00'; - ", - ); - - let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); - let handle = runtime.load_module(&module).unwrap(); - - let f: String = runtime.get_value(Some(&handle), "good").unwrap(); - let value = f.to_string_lossy(&mut runtime); - assert_eq!(value, "Hello, World!"); - - let f: String = runtime.get_value(Some(&handle), "good").unwrap(); - let value = f.to_string(&mut runtime).unwrap(); - assert_eq!(value, "Hello, World!"); - - let f: String = runtime.get_value(Some(&handle), "bad").unwrap(); - let value = f.to_utf16_bytes(&mut runtime); - assert_eq!(value, vec![0xd83d, 0xde00]); - } -} +use deno_core::v8::{self, WriteFlags}; +use serde::Deserialize; + +use super::V8Value; + +/// A Deserializable javascript UTF-16 string, that can be stored and used later +/// Must live as long as the runtime it was birthed from +#[derive(Eq, Hash, PartialEq, Debug, Clone)] +pub struct String(V8Value); +impl_v8!(String, StringTypeChecker); +impl_checker!(StringTypeChecker, String, is_string, |e| { + crate::Error::JsonDecode(format!("Expected a string, found `{e}`")) +}); + +impl String { + /// Converts the string to a rust string + /// Potentially lossy, if the string contains orphan UTF-16 surrogates + pub fn to_string_lossy(&self, runtime: &mut crate::Runtime) -> std::string::String { + let isolate = runtime.deno_runtime().v8_isolate(); + let pinned_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let scope = pinned_scope.init(); + self.to_rust_string_lossy(&scope) + } + + /// Converts the string to a rust string + /// If the string contains orphan UTF-16 surrogates, it may return None + /// In that case, you can use `to_string_lossy` to get a lossy conversion + pub fn to_string(&self, runtime: &mut crate::Runtime) -> Option { + let bytes = self.to_utf8_bytes(runtime); + std::string::String::from_utf8(bytes).ok() + } + + /// Converts the string to a UTF-8 character buffer in the form of a `Vec` + /// Excludes the null terminator + pub fn to_utf8_bytes(&self, runtime: &mut crate::Runtime) -> Vec { + let isolate = runtime.deno_runtime().v8_isolate(); + let pinned_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let scope = pinned_scope.init(); + self.to_utf8_buffer(&scope) + } + + /// Converts the string to a UTF-16 character buffer in the form of a `Vec` + /// Excludes the null terminator + pub fn to_utf16_bytes(&self, runtime: &mut crate::Runtime) -> Vec { + let isolate = runtime.deno_runtime().v8_isolate(); + let pinned_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let scope = pinned_scope.init(); + self.to_utf16_buffer(&scope) + } + + pub(crate) fn to_rust_string_lossy( + &self, + scope: &v8::PinnedRef<'_, v8::HandleScope<'_, C>>, + ) -> std::string::String { + let local = self.0.as_local(scope); + // SAFETY: v8::String::to_rust_string_lossy requires &Isolate. PinnedRef can be transmuted to &Isolate. + let isolate = unsafe { &*std::ptr::from_ref(scope).cast::() }; + local.to_rust_string_lossy(isolate) + } + + pub(crate) fn to_utf16_buffer( + &self, + scope: &v8::PinnedRef<'_, v8::HandleScope<'_, C>>, + ) -> Vec { + let local = self.0.as_local(scope); + let u16_len = local.length(); + let mut buffer = vec![0; u16_len]; + + // SAFETY: v8 methods may require &Isolate. PinnedRef can be transmuted to &Isolate. + let isolate = unsafe { &*std::ptr::from_ref(scope).cast::() }; + local.write_v2(isolate, 0, &mut buffer, WriteFlags::empty()); + buffer + } + + pub(crate) fn to_utf8_buffer( + &self, + scope: &v8::PinnedRef<'_, v8::HandleScope<'_, C>>, + ) -> Vec { + let local = self.0.as_local(scope); + // SAFETY: v8 methods may require &Isolate. PinnedRef can be transmuted to &Isolate. + let isolate = unsafe { &*std::ptr::from_ref(scope).cast::() }; + let u8_len = local.utf8_length(isolate); + let mut buffer = vec![0; u8_len]; + + local.write_utf8_v2(isolate, &mut buffer, WriteFlags::empty(), None); + buffer + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{Module, Runtime, RuntimeOptions}; + + #[test] + fn test_string() { + let module = Module::new( + "test.js", + " + // Valid UTF-8 + export const good = 'Hello, World!'; + + // Invalid UTF-8, valid UTF-16 + export const bad = '\\ud83d\\ude00'; + ", + ); + + let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); + let handle = runtime.load_module(&module).unwrap(); + + let f: String = runtime.get_value(Some(&handle), "good").unwrap(); + let value = f.to_string_lossy(&mut runtime); + assert_eq!(value, "Hello, World!"); + + let f: String = runtime.get_value(Some(&handle), "good").unwrap(); + let value = f.to_string(&mut runtime).unwrap(); + assert_eq!(value, "Hello, World!"); + + let f: String = runtime.get_value(Some(&handle), "bad").unwrap(); + let value = f.to_utf16_bytes(&mut runtime); + assert_eq!(value, vec![0xd83d, 0xde00]); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8486945..afdb02b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ //! - Load a javascript module, //! - Call a function registered as the entrypoint //! - Return the resulting value -//! ```rust +//! ```no_run //! use rustyscript::{json_args, Runtime, Module, Error}; //! //! # fn main() -> Result<(), Error> { @@ -64,7 +64,7 @@ //! ---- //! //! If all you need is the result of a single javascript expression, you can use: -//! ```rust +//! ```no_run //! let result: i64 = rustyscript::evaluate("5 + 5").expect("The expression was invalid!"); //! ``` //! @@ -80,7 +80,7 @@ //! ---- //! //! A more detailed version of the crate's usage can be seen below, which breaks down the steps instead of using the one-liner [`Runtime::execute_module`]: -//! ```rust +//! ```no_run //! use rustyscript::{json_args, Runtime, RuntimeOptions, Module, Error, Undefined}; //! use std::time::Duration; //! @@ -120,7 +120,7 @@ //! for using [`crate::js_value::Promise`] //! //! Rust functions can also be registered to be called from javascript: -//! ```rust +//! ```no_run //! use rustyscript::{ Runtime, Module, serde_json::Value }; //! //! # fn main() -> Result<(), rustyscript::Error> { @@ -142,7 +142,7 @@ //! Asynchronous JS can be called in 2 ways; //! //! The first is to use the 'async' keyword in JS, and then call the function using [`Runtime::call_function_async`] -//! ```rust +//! ```no_run //! use rustyscript::{ Runtime, Module, json_args }; //! //! # fn main() -> Result<(), rustyscript::Error> { @@ -167,7 +167,7 @@ //! ``` //! //! The second is to use [`crate::js_value::Promise`] -//! ```rust +//! ```no_run //! use rustyscript::{ Runtime, Module, js_value::Promise, json_args }; //! //! # fn main() -> Result<(), rustyscript::Error> { @@ -340,6 +340,10 @@ pub mod extensions { #[cfg(feature = "console")] #[cfg_attr(docsrs, doc(cfg(feature = "console")))] + #[deprecated( + since = "0.13.0", + note = "deno_console is deprecated upstream. Console functionality is now integrated into deno_web. Use the JavaScript console API directly." + )] pub use deno_console; #[cfg(feature = "cron")] @@ -421,9 +425,18 @@ pub use module_wrapper::ModuleWrapper; pub use runtime::{Runtime, RuntimeOptions, Undefined}; pub use utilities::{evaluate, import, init_platform, resolve_path, validate}; -#[cfg(feature = "broadcast_channel")] -#[cfg_attr(docsrs, doc(cfg(feature = "broadcast_channel")))] -pub use ext::broadcast_channel::BroadcastChannelWrapper; +// BREAKING CHANGE: BroadcastChannelWrapper has been removed. +// The upstream `deno_broadcast_channel` crate is now deprecated and its API +// has been made private in `deno_web`. Use the JavaScript BroadcastChannel API instead: +// +// ```javascript +// const channel = new BroadcastChannel("my-channel"); +// channel.postMessage({ data: "hello" }); +// channel.onmessage = (event) => console.log(event.data); +// ``` +// +// For cross-runtime communication, consider using SharedArrayBuffer or +// the runtime's state management features. #[cfg(feature = "web")] #[cfg_attr(docsrs, doc(cfg(feature = "web")))] diff --git a/src/module.rs b/src/module.rs index 4cec329..958c461 100644 --- a/src/module.rs +++ b/src/module.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; /// /// # Example /// -/// ```rust +/// ```no_run /// use rustyscript::{ module, Module }; /// /// const MY_SCRIPT: Module = module!( @@ -101,7 +101,7 @@ impl Module { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::Module; /// /// let module = Module::new("module.js", "console.log('Hello, World!');"); @@ -128,7 +128,7 @@ impl Module { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::Module; /// /// let module = Module::new("module.js", "console.log('Hello, World!');"); @@ -155,7 +155,7 @@ impl Module { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::Module; /// /// # fn main() -> Result<(), rustyscript::Error> { @@ -184,7 +184,7 @@ impl Module { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::Module; /// /// # fn main() -> Result<(), rustyscript::Error> { @@ -220,7 +220,7 @@ impl Module { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::Module; /// /// let module = Module::new("module.js", "console.log('Hello, World!');"); @@ -238,7 +238,7 @@ impl Module { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::Module; /// /// let module = Module::new("module.js", "console.log('Hello, World!');"); diff --git a/src/module_loader.rs b/src/module_loader.rs index d5ecacd..66912ff 100644 --- a/src/module_loader.rs +++ b/src/module_loader.rs @@ -1,269 +1,271 @@ -//! Module loader implementation for rustyscript -//! This module provides tools for caching module data, resolving module specifiers, and loading modules -#![allow(deprecated)] - -use std::{borrow::Cow, cell::RefCell, path::PathBuf, rc::Rc}; - -use deno_core::{error::ModuleLoaderError, ModuleLoader, ModuleSpecifier}; - -mod inner_loader; -use inner_loader::InnerRustyLoader; -pub(crate) use inner_loader::LoaderOptions; - -mod cache_provider; -pub use cache_provider::{ClonableSource, ModuleCacheProvider}; - -mod import_provider; -pub use import_provider::ImportProvider; - -use crate::transpiler::ExtensionTranspiler; - -/// The primary module loader implementation for rustyscript -/// This structure manages fetching module code, transpilation, and caching -pub(crate) struct RustyLoader { - inner: Rc>, -} -impl RustyLoader { - /// Creates a new instance of `RustyLoader` - /// An optional cache provider can be provided to manage module code caching, as well as an import provider to manage module resolution. - pub fn new(options: LoaderOptions) -> Self { - let inner = Rc::new(RefCell::new(InnerRustyLoader::new(options))); - Self { inner } - } - - pub fn set_current_dir(&self, current_dir: PathBuf) { - self.inner_mut().set_current_dir(current_dir); - } - - fn inner(&self) -> std::cell::Ref<'_, InnerRustyLoader> { - self.inner.borrow() - } - - fn inner_mut(&self) -> std::cell::RefMut<'_, InnerRustyLoader> { - self.inner.borrow_mut() - } - - /// Inserts a source map into the source map cache - /// This is used to provide source maps for loaded modules - /// for error message generation - pub fn insert_source_map(&self, file_name: &str, code: String, source_map: Option>) { - self.inner_mut().add_source_map(file_name, code, source_map); - } - - /// Get an extension transpiler that can be injected into a `deno_core::JsRuntime` - pub fn as_extension_transpiler(self: &Rc) -> ExtensionTranspiler { - let loader = self.clone(); - Rc::new(move |specifier, code| loader.inner().transpile_extension(&specifier, &code)) - } - - /// Transpile a module from CJS to ESM - #[allow(dead_code)] - pub async fn translate_cjs( - &self, - specifier: &ModuleSpecifier, - source: &str, - ) -> Result { - InnerRustyLoader::translate_cjs(self.inner.clone(), specifier.clone(), source.to_string()) - .await - } -} - -// -// Deno trait implementations start -// - -impl ModuleLoader for RustyLoader { - /// Resolve a module specifier to a full url by adding the base url - /// and resolving any relative paths - /// - /// Also checks if the module is allowed to be loaded or not based on scheme - fn resolve( - &self, - specifier: &str, - referrer: &str, - kind: deno_core::ResolutionKind, - ) -> Result { - self.inner_mut().resolve(specifier, referrer, kind) - } - - /// Load a module by it's name - fn load( - &self, - module_specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - is_dyn_import: bool, - requested_module_type: deno_core::RequestedModuleType, - ) -> deno_core::ModuleLoadResponse { - let inner = self.inner.clone(); - InnerRustyLoader::load( - inner, - module_specifier, - maybe_referrer, - is_dyn_import, - requested_module_type, - ) - } - - fn get_source_map(&self, file_name: &str) -> Option> { - let inner = self.inner(); - let map = inner.get_source_map(file_name)?.1.as_deref()?; - Some(Cow::Owned(map.to_vec())) - } - - fn get_source_mapped_source_line(&self, file_name: &str, line_number: usize) -> Option { - let inner = self.inner(); - let lines: Vec<_> = inner.get_source_map(file_name)?.0.split('\n').collect(); - if line_number >= lines.len() { - return None; - } - Some(lines[line_number].to_string()) - } -} - -#[cfg(test)] -mod test { - use deno_core::{ - ModuleLoadResponse, ModuleSource, ModuleSourceCode, ModuleType, ResolutionKind, - }; - - use super::*; - use crate::{module_loader::ClonableSource, traits::ToModuleSpecifier}; - - /// Test in-memory module cache provider - #[derive(Default)] - struct MemoryModuleCacheProvider(std::collections::HashMap); - impl ModuleCacheProvider for MemoryModuleCacheProvider { - fn set(&mut self, specifier: &ModuleSpecifier, source: ModuleSource) { - self.0.insert(specifier.clone(), source); - } - - fn get(&self, specifier: &ModuleSpecifier) -> Option { - self.0.get(specifier).map(|s| s.clone(specifier)) - } - } - - #[tokio::test] - async fn test_loader() { - let mut cache_provider = MemoryModuleCacheProvider::default(); - let specifier = "file:///test.ts" - .to_module_specifier(&std::env::current_dir().unwrap()) - .unwrap(); - let source = ModuleSource::new( - ModuleType::JavaScript, - ModuleSourceCode::String("console.log('Hello, World!')".to_string().into()), - &specifier, - None, - ); - - cache_provider.set(&specifier, source.clone(&specifier)); - let cached_source = cache_provider - .get(&specifier) - .expect("Expected to get cached source"); - - let loader = RustyLoader::new(LoaderOptions { - cache_provider: Some(Box::new(cache_provider)), - ..LoaderOptions::default() - }); - let response = loader.load( - &specifier, - None, - false, - deno_core::RequestedModuleType::None, - ); - match response { - ModuleLoadResponse::Async(_) => panic!("Unexpected response"), - ModuleLoadResponse::Sync(result) => { - let source = result.expect("Expected to get source"); - - let ModuleSourceCode::String(source) = source.code else { - panic!("Unexpected source code type"); - }; - - let ModuleSourceCode::String(cached_source) = cached_source.code else { - panic!("Unexpected source code type"); - }; - - assert_eq!(source, cached_source); - } - } - } - - struct TestImportProvider { - i: usize, - } - impl TestImportProvider { - fn new() -> Self { - Self { i: 0 } - } - } - impl ImportProvider for TestImportProvider { - fn resolve( - &mut self, - specifier: &ModuleSpecifier, - _referrer: &str, - _kind: deno_core::ResolutionKind, - ) -> Option> { - match specifier.scheme() { - "test" => { - self.i += 1; - Some(Ok( - ModuleSpecifier::parse(&format!("test://{}", self.i)).unwrap() - )) - } - _ => None, - } - } - fn import( - &mut self, - specifier: &ModuleSpecifier, - _referrer: Option<&ModuleSpecifier>, - _is_dyn_import: bool, - _requested_module_type: deno_core::RequestedModuleType, - ) -> Option> { - match specifier.as_str() { - "test://1" => Some(Ok("console.log('Rock')".to_string())), - "test://2" => Some(Ok("console.log('Paper')".to_string())), - "test://3" => Some(Ok("console.log('Scissors')".to_string())), - _ => None, - } - } - } - - #[tokio::test] - async fn test_import_provider() { - let loader = RustyLoader::new(LoaderOptions { - import_provider: Some(Box::new(TestImportProvider::new())), - cwd: std::env::current_dir().unwrap(), - ..LoaderOptions::default() - }); - let expected_responses = [ - "console.log('Rock')".to_string(), - "console.log('Paper')".to_string(), - "console.log('Scissors')".to_string(), - ]; - - for expected in expected_responses { - let specifier = loader - .resolve("test://anything", "", ResolutionKind::Import) - .unwrap(); - let response = loader.load( - &specifier, - None, - false, - deno_core::RequestedModuleType::None, - ); - match response { - ModuleLoadResponse::Async(future) => { - let source = future.await.expect("Expected to get source"); - let ModuleSourceCode::String(source) = source.code else { - panic!("Unexpected source code type"); - }; - - assert_eq!(source, expected.into()); - } - - ModuleLoadResponse::Sync(_) => panic!("Unexpected response"), - } - } - } -} +//! Module loader implementation for rustyscript +//! This module provides tools for caching module data, resolving module specifiers, and loading modules +#![allow(deprecated)] + +use std::{borrow::Cow, cell::RefCell, path::PathBuf, rc::Rc}; + +use deno_core::{error::ModuleLoaderError, ModuleLoader, ModuleSpecifier}; + +mod inner_loader; +use inner_loader::InnerRustyLoader; +pub(crate) use inner_loader::LoaderOptions; + +mod cache_provider; +pub use cache_provider::{ClonableSource, ModuleCacheProvider}; + +mod import_provider; +pub use import_provider::ImportProvider; + +use crate::transpiler::ExtensionTranspiler; + +/// The primary module loader implementation for rustyscript +/// This structure manages fetching module code, transpilation, and caching +pub(crate) struct RustyLoader { + inner: Rc>, +} +impl RustyLoader { + /// Creates a new instance of `RustyLoader` + /// An optional cache provider can be provided to manage module code caching, as well as an import provider to manage module resolution. + pub fn new(options: LoaderOptions) -> Self { + let inner = Rc::new(RefCell::new(InnerRustyLoader::new(options))); + Self { inner } + } + + pub fn set_current_dir(&self, current_dir: PathBuf) { + self.inner_mut().set_current_dir(current_dir); + } + + pub fn whitelist_add(&self, specifier: &ModuleSpecifier) { + self.inner_mut().whitelist_add(specifier.as_str()); + } + + fn inner(&self) -> std::cell::Ref<'_, InnerRustyLoader> { + self.inner.borrow() + } + + fn inner_mut(&self) -> std::cell::RefMut<'_, InnerRustyLoader> { + self.inner.borrow_mut() + } + + /// Inserts a source map into the source map cache + /// This is used to provide source maps for loaded modules + /// for error message generation + pub fn insert_source_map(&self, file_name: &str, code: String, source_map: Option>) { + self.inner_mut().add_source_map(file_name, code, source_map); + } + + /// Get an extension transpiler that can be injected into a `deno_core::JsRuntime` + pub fn as_extension_transpiler(self: &Rc) -> ExtensionTranspiler { + let loader = self.clone(); + Rc::new(move |specifier, code| loader.inner().transpile_extension(&specifier, &code)) + } + + /// Transpile a module from CJS to ESM + #[allow(dead_code)] + pub async fn translate_cjs( + &self, + specifier: &ModuleSpecifier, + source: &str, + ) -> Result { + InnerRustyLoader::translate_cjs(self.inner.clone(), specifier.clone(), source.to_string()) + .await + } +} + +// +// Deno trait implementations start +// + +impl ModuleLoader for RustyLoader { + /// Resolve a module specifier to a full url by adding the base url + /// and resolving any relative paths + /// + /// Also checks if the module is allowed to be loaded or not based on scheme + fn resolve( + &self, + specifier: &str, + referrer: &str, + kind: deno_core::ResolutionKind, + ) -> Result { + self.inner_mut().resolve(specifier, referrer, kind) + } + + /// Load a module by it's name + fn load( + &self, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option<&deno_core::ModuleLoadReferrer>, + options: deno_core::ModuleLoadOptions, + ) -> deno_core::ModuleLoadResponse { + let inner = self.inner.clone(); + InnerRustyLoader::load(inner, module_specifier, maybe_referrer, options) + } + + fn get_source_map(&self, file_name: &str) -> Option> { + let inner = self.inner(); + let map = inner.get_source_map(file_name)?.1.as_deref()?; + Some(Cow::Owned(map.to_vec())) + } + + fn get_source_mapped_source_line(&self, file_name: &str, line_number: usize) -> Option { + let inner = self.inner(); + let lines: Vec<_> = inner.get_source_map(file_name)?.0.split('\n').collect(); + if line_number >= lines.len() { + return None; + } + Some(lines[line_number].to_string()) + } +} + +#[cfg(test)] +mod test { + use deno_core::{ + ModuleLoadResponse, ModuleSource, ModuleSourceCode, ModuleType, ResolutionKind, + }; + + use super::*; + use crate::{module_loader::ClonableSource, traits::ToModuleSpecifier}; + + /// Test in-memory module cache provider + #[derive(Default)] + struct MemoryModuleCacheProvider(std::collections::HashMap); + impl ModuleCacheProvider for MemoryModuleCacheProvider { + fn set(&mut self, specifier: &ModuleSpecifier, source: ModuleSource) { + self.0.insert(specifier.clone(), source); + } + + fn get(&self, specifier: &ModuleSpecifier) -> Option { + self.0.get(specifier).map(|s| s.clone(specifier)) + } + } + + #[tokio::test] + async fn test_loader() { + let mut cache_provider = MemoryModuleCacheProvider::default(); + let specifier = "file:///test.ts" + .to_module_specifier(&std::env::current_dir().unwrap()) + .unwrap(); + let source = ModuleSource::new( + ModuleType::JavaScript, + ModuleSourceCode::String("console.log('Hello, World!')".to_string().into()), + &specifier, + None, + ); + + cache_provider.set(&specifier, source.clone(&specifier)); + let cached_source = cache_provider + .get(&specifier) + .expect("Expected to get cached source"); + + let loader = RustyLoader::new(LoaderOptions { + cache_provider: Some(Box::new(cache_provider)), + ..LoaderOptions::default() + }); + let response = loader.load( + &specifier, + None, + deno_core::ModuleLoadOptions { + is_dynamic_import: false, + is_synchronous: false, + requested_module_type: deno_core::RequestedModuleType::None, + }, + ); + match response { + ModuleLoadResponse::Async(_) => panic!("Unexpected response"), + ModuleLoadResponse::Sync(result) => { + let source = result.expect("Expected to get source"); + + let ModuleSourceCode::String(source) = source.code else { + panic!("Unexpected source code type"); + }; + + let ModuleSourceCode::String(cached_source) = cached_source.code else { + panic!("Unexpected source code type"); + }; + + assert_eq!(source, cached_source); + } + } + } + + struct TestImportProvider { + i: usize, + } + impl TestImportProvider { + fn new() -> Self { + Self { i: 0 } + } + } + impl ImportProvider for TestImportProvider { + fn resolve( + &mut self, + specifier: &ModuleSpecifier, + _referrer: &str, + _kind: deno_core::ResolutionKind, + ) -> Option> { + match specifier.scheme() { + "test" => { + self.i += 1; + Some(Ok( + ModuleSpecifier::parse(&format!("test://{}", self.i)).unwrap() + )) + } + _ => None, + } + } + fn import( + &mut self, + specifier: &ModuleSpecifier, + _referrer: Option<&ModuleSpecifier>, + _is_dyn_import: bool, + ) -> Option> { + match specifier.as_str() { + "test://1" => Some(Ok("console.log('Rock')".to_string())), + "test://2" => Some(Ok("console.log('Paper')".to_string())), + "test://3" => Some(Ok("console.log('Scissors')".to_string())), + _ => None, + } + } + } + + #[tokio::test] + async fn test_import_provider() { + let loader = RustyLoader::new(LoaderOptions { + import_provider: Some(Box::new(TestImportProvider::new())), + cwd: std::env::current_dir().unwrap(), + ..LoaderOptions::default() + }); + let expected_responses = [ + "console.log('Rock')".to_string(), + "console.log('Paper')".to_string(), + "console.log('Scissors')".to_string(), + ]; + + for expected in expected_responses { + let specifier = loader + .resolve("test://anything", "", ResolutionKind::Import) + .unwrap(); + let response = loader.load( + &specifier, + None, + deno_core::ModuleLoadOptions { + is_dynamic_import: false, + is_synchronous: false, + requested_module_type: deno_core::RequestedModuleType::None, + }, + ); + match response { + ModuleLoadResponse::Async(future) => { + let source = future.await.expect("Expected to get source"); + let ModuleSourceCode::String(source) = source.code else { + panic!("Unexpected source code type"); + }; + + assert_eq!(source, expected.into()); + } + + ModuleLoadResponse::Sync(_) => panic!("Unexpected response"), + } + } + } +} diff --git a/src/module_loader/import_provider.rs b/src/module_loader/import_provider.rs index 8e23303..ace1ed8 100644 --- a/src/module_loader/import_provider.rs +++ b/src/module_loader/import_provider.rs @@ -1,4 +1,4 @@ -use deno_core::{error::ModuleLoaderError, ModuleSource, ModuleSpecifier, RequestedModuleType}; +use deno_core::{error::ModuleLoaderError, ModuleSource, ModuleSpecifier}; /// A trait that can be implemented to modify the behavior of the module loader /// Allows for custom schemes, caching, and more granular permissions @@ -33,7 +33,6 @@ pub trait ImportProvider { /// - `specifier`: The module specifier to import, as an absolute URL /// - `referrer`: The URL of the module that is importing the specifier /// - `is_dyn_import`: Whether the import is a dynamic import or not - /// - `requested_module_type`: The type of module being requested /// /// # Returns /// - Some(Ok(String)): The module source code as a string @@ -44,7 +43,6 @@ pub trait ImportProvider { specifier: &ModuleSpecifier, referrer: Option<&ModuleSpecifier>, is_dyn_import: bool, - requested_module_type: RequestedModuleType, ) -> Option> { None } diff --git a/src/module_loader/inner_loader.rs b/src/module_loader/inner_loader.rs index 04846b7..d73532a 100644 --- a/src/module_loader/inner_loader.rs +++ b/src/module_loader/inner_loader.rs @@ -274,12 +274,12 @@ impl InnerRustyLoader { pub fn load( inner: Rc>, module_specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - is_dyn_import: bool, - requested_module_type: deno_core::RequestedModuleType, + maybe_referrer: Option<&deno_core::ModuleLoadReferrer>, + options: deno_core::ModuleLoadOptions, ) -> deno_core::ModuleLoadResponse { let module_specifier = module_specifier.clone(); - let maybe_referrer = maybe_referrer.cloned(); + let maybe_referrer_url = maybe_referrer.map(|r| r.specifier.clone()); + let is_dyn_import = options.is_dynamic_import; // Check if the module is in the cache first if let Some(cache) = &inner.borrow().cache_provider { @@ -292,9 +292,8 @@ impl InnerRustyLoader { let provider_result = inner.borrow_mut().import_provider.as_mut().and_then(|p| { p.import( &module_specifier, - maybe_referrer.as_ref(), + maybe_referrer_url.as_ref(), is_dyn_import, - requested_module_type, ) }); if let Some(result) = provider_result { diff --git a/src/module_wrapper.rs b/src/module_wrapper.rs index 2085641..d397426 100644 --- a/src/module_wrapper.rs +++ b/src/module_wrapper.rs @@ -1,388 +1,394 @@ -use deno_core::{serde_json, v8::GetPropertyNamesArgs}; - -use crate::{js_value::Function, Error, Module, ModuleHandle, Runtime, RuntimeOptions}; - -/// A wrapper type representing a runtime instance loaded with a single module -/// -/// Exactly equivalent to [`Runtime::new`] followed by [`Runtime::load_module`] -/// -/// Can also be created using the [`crate::import`] function -pub struct ModuleWrapper { - module_context: ModuleHandle, - runtime: Runtime, -} - -impl ModuleWrapper { - /// Creates a new `ModuleWrapper` from a given module and runtime options. - /// - /// # Arguments - /// * `module` - A reference to the module to load. - /// * `options` - The runtime options for the module. - /// - /// # Returns - /// A `Result` containing `Self` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if module execution fails - pub fn new_from_module(module: &Module, options: RuntimeOptions) -> Result { - let mut runtime = Runtime::new(options)?; - let module_context = runtime.load_module(module)?; - Ok(Self { - module_context, - runtime, - }) - } - - /// Creates a new `ModuleWrapper` from a file path and runtime options. - /// - /// # Arguments - /// * `path` - The path to the module file. - /// * `options` - The runtime options for the module. - /// - /// # Returns - /// A `Result` containing `Self` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the file cannot be loaded, or if module execution fails - pub fn new_from_file(path: &str, options: RuntimeOptions) -> Result { - let module = Module::load(path)?; - Self::new_from_module(&module, options) - } - - /// Returns a reference to the module context. - #[must_use] - pub fn get_module_context(&self) -> &ModuleHandle { - &self.module_context - } - - /// Returns a mutable reference to the underlying runtime. - pub fn get_runtime(&mut self) -> &mut Runtime { - &mut self.runtime - } - - /// Retrieves a value from the module by name and deserializes it. - /// - /// See [`Runtime::get_value`] - /// - /// # Arguments - /// * `name` - The name of the value to retrieve. - /// - /// # Returns - /// A `Result` containing the deserialized value of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the value cannot be found, or deserialized into the given type - pub fn get(&mut self, name: &str) -> Result - where - T: serde::de::DeserializeOwned, - { - self.runtime.get_value(Some(&self.module_context), name) - } - - /// Retrieves a future resolving to a value from the module by name and deserializes it. - /// - /// See [`Runtime::get_value_async`] - /// - /// # Arguments - /// * `name` - The name of the value to retrieve. - /// - /// # Returns - /// A `Result` containing the deserialized value of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the value cannot be found, or deserialized into the given type - pub async fn get_async(&mut self, name: &str) -> Result - where - T: serde::de::DeserializeOwned, - { - self.runtime - .get_value_async(Some(&self.module_context), name) - .await - } - - /// Retrieves a value from the module by name and deserializes it. - /// - /// Does not await promises or the event loop. - /// - /// See [`Runtime::get_value_immediate`] - /// - /// # Arguments - /// * `name` - The name of the value to retrieve. - /// - /// # Returns - /// A `Result` containing the deserialized value of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the value cannot be found, or deserialized into the given type - pub fn get_immediate(&mut self, name: &str) -> Result - where - T: serde::de::DeserializeOwned, - { - self.runtime - .get_value_immediate(Some(&self.module_context), name) - } - - /// Checks if a value in the module with the given name is callable as a JavaScript function. - /// - /// # Arguments - /// * `name` - The name of the value to check for callability. - /// - /// # Returns - /// `true` if the value is callable as a JavaScript function, `false` otherwise. - pub fn is_callable(&mut self, name: &str) -> bool { - let test = self.get::(name); - test.is_ok() - } - - /// Calls a function in the module with the given name and arguments and deserializes the result. - /// - /// See [`Runtime::call_function`] - /// - /// # Arguments - /// * `name` - The name of the function to call. - /// * `args` - The arguments to pass to the function. - /// - /// # Returns - /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the function cannot be called, if the function returns an error, - /// or if the function returns a value that cannot be deserialized into the given type - pub fn call(&mut self, name: &str, args: &impl serde::ser::Serialize) -> Result - where - T: serde::de::DeserializeOwned, - { - self.runtime - .call_function(Some(&self.module_context), name, args) - } - - /// Calls a function in the module with the given name and arguments and deserializes the result. - /// - /// See [`Runtime::call_function_async`] - /// - /// # Arguments - /// * `name` - The name of the function to call. - /// * `args` - The arguments to pass to the function. - /// - /// # Returns - /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the function cannot be called, if the function returns an error, - /// or if the function returns a value that cannot be deserialized into the given type - pub async fn call_async( - &mut self, - name: &str, - args: &impl serde::ser::Serialize, - ) -> Result { - self.runtime - .call_function_async(Some(&self.module_context), name, args) - .await - } - - /// Calls a function in the module with the given name and arguments and deserializes the result. - /// Does not await promises or the event loop. - /// - /// See [`Runtime::call_function_immediate`] - /// - /// # Arguments - /// * `name` - The name of the function to call. - /// * `args` - The arguments to pass to the function. - /// - /// # Returns - /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the function cannot be called, if the function returns an error, - /// or if the function returns a value that cannot be deserialized into the given type - pub fn call_immediate( - &mut self, - name: &str, - args: &impl serde::ser::Serialize, - ) -> Result { - self.runtime - .call_function_immediate(Some(&self.module_context), name, args) - } - - /// Calls a function using the module's runtime that was previously stored as a Function object - /// - /// See [`Runtime::call_stored_function`] - /// - /// # Arguments - /// * `function` - The Function to call. - /// * `args` - The arguments to pass to the function. - /// - /// # Returns - /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the function cannot be called, if the function returns an error, - /// or if the function returns a value that cannot be deserialized into the given type - pub fn call_stored( - &mut self, - function: &Function, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: serde::de::DeserializeOwned, - { - self.runtime - .call_stored_function(Some(&self.module_context), function, args) - } - - /// Calls a function using the module's runtime that was previously stored as a Function object - /// - /// See [`Runtime::call_stored_function_async`] - /// - /// # Arguments - /// * `function` - The Function to call. - /// * `args` - The arguments to pass to the function. - /// - /// # Returns - /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the function cannot be called, if the function returns an error, - /// or if the function returns a value that cannot be deserialized into the given type - pub async fn call_stored_async( - &mut self, - function: &Function, - args: &impl serde::ser::Serialize, - ) -> Result { - self.runtime - .call_stored_function_async(Some(&self.module_context), function, args) - .await - } - - /// Calls a function using the module's runtime that was previously stored as a Function object - /// - /// Does not await promises or the event loop. - /// - /// See [`Runtime::call_stored_function_immediate`] - /// - /// # Arguments - /// * `function` - The Function to call. - /// * `args` - The arguments to pass to the function. - /// - /// # Returns - /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. - /// - /// # Errors - /// Will return an error if the function cannot be called, if the function returns an error, - /// or if the function returns a value that cannot be deserialized into the given type - pub fn call_stored_immediate( - &mut self, - function: &Function, - args: &impl serde::ser::Serialize, - ) -> Result { - self.runtime - .call_stored_function_immediate(Some(&self.module_context), function, args) - } - - /// Retrieves the names of the module's exports. - /// (Keys that are not valid UTF-8, may not work as intended due to encoding issues) - /// - /// # Returns - /// A `Vec` of `String` containing the names of the keys. - pub fn keys(&mut self) -> Vec { - let mut keys: Vec = Vec::new(); - if let Ok(namespace) = self - .runtime - .deno_runtime() - .get_module_namespace(self.module_context.id()) - { - let mut scope = self.runtime.deno_runtime().handle_scope(); - let global = namespace.open(&mut scope); - if let Some(keys_obj) = - global.get_property_names(&mut scope, GetPropertyNamesArgs::default()) - { - for i in 0..keys_obj.length() { - if let Ok(key_index) = deno_core::serde_v8::to_v8(&mut scope, i) { - if let Some(key_name_v8) = keys_obj.get(&mut scope, key_index) { - let name = key_name_v8.to_rust_string_lossy(&mut scope); - keys.push(name); - } - } - } - } - } - - keys - } -} - -#[cfg(test)] -mod test_runtime { - use super::*; - use crate::json_args; - - #[test] - fn test_call() { - let module = Module::new( - "test.js", - " - console.log('test'); - export const value = 3; - export function func() { return 4; } - ", - ); - - let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) - .expect("Could not create wrapper"); - let value: usize = module - .call("func", json_args!()) - .expect("Could not call function"); - assert_eq!(4, value); - } - - #[test] - fn test_get() { - let module = Module::new( - "test.js", - " - export const value = 3; - export function func() { return 4; } - ", - ); - - let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) - .expect("Could not create wrapper"); - let value: usize = module.get("value").expect("Could not get value"); - assert_eq!(3, value); - } - - #[test] - fn test_callable() { - let module = Module::new( - "test.js", - " - export const value = 3; - export function func() { return 4; } - ", - ); - - let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) - .expect("Could not create wrapper"); - - assert!(module.is_callable("func")); - assert!(!module.is_callable("value")); - } - - #[test] - fn test_keys() { - let module = Module::new( - "test.js", - " - export const value = 3; - export function func() { return 4; } - ", - ); - - let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) - .expect("Could not create wrapper"); - let mut keys = module.keys(); - assert_eq!(2, keys.len()); - assert_eq!("value", keys.pop().unwrap()); - assert_eq!("func", keys.pop().unwrap()); - } -} +use deno_core::{serde_json, v8, v8::GetPropertyNamesArgs}; + +use crate::{js_value::Function, Error, Module, ModuleHandle, Runtime, RuntimeOptions}; + +/// A wrapper type representing a runtime instance loaded with a single module +/// +/// Exactly equivalent to [`Runtime::new`] followed by [`Runtime::load_module`] +/// +/// Can also be created using the [`crate::import`] function +pub struct ModuleWrapper { + module_context: ModuleHandle, + runtime: Runtime, +} + +impl ModuleWrapper { + /// Creates a new `ModuleWrapper` from a given module and runtime options. + /// + /// # Arguments + /// * `module` - A reference to the module to load. + /// * `options` - The runtime options for the module. + /// + /// # Returns + /// A `Result` containing `Self` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if module execution fails + pub fn new_from_module(module: &Module, options: RuntimeOptions) -> Result { + let mut runtime = Runtime::new(options)?; + let module_context = runtime.load_module(module)?; + Ok(Self { + module_context, + runtime, + }) + } + + /// Creates a new `ModuleWrapper` from a file path and runtime options. + /// + /// # Arguments + /// * `path` - The path to the module file. + /// * `options` - The runtime options for the module. + /// + /// # Returns + /// A `Result` containing `Self` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the file cannot be loaded, or if module execution fails + pub fn new_from_file(path: &str, options: RuntimeOptions) -> Result { + let module = Module::load(path)?; + Self::new_from_module(&module, options) + } + + /// Returns a reference to the module context. + #[must_use] + pub fn get_module_context(&self) -> &ModuleHandle { + &self.module_context + } + + /// Returns a mutable reference to the underlying runtime. + pub fn get_runtime(&mut self) -> &mut Runtime { + &mut self.runtime + } + + /// Retrieves a value from the module by name and deserializes it. + /// + /// See [`Runtime::get_value`] + /// + /// # Arguments + /// * `name` - The name of the value to retrieve. + /// + /// # Returns + /// A `Result` containing the deserialized value of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the value cannot be found, or deserialized into the given type + pub fn get(&mut self, name: &str) -> Result + where + T: serde::de::DeserializeOwned, + { + self.runtime.get_value(Some(&self.module_context), name) + } + + /// Retrieves a future resolving to a value from the module by name and deserializes it. + /// + /// See [`Runtime::get_value_async`] + /// + /// # Arguments + /// * `name` - The name of the value to retrieve. + /// + /// # Returns + /// A `Result` containing the deserialized value of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the value cannot be found, or deserialized into the given type + pub async fn get_async(&mut self, name: &str) -> Result + where + T: serde::de::DeserializeOwned, + { + self.runtime + .get_value_async(Some(&self.module_context), name) + .await + } + + /// Retrieves a value from the module by name and deserializes it. + /// + /// Does not await promises or the event loop. + /// + /// See [`Runtime::get_value_immediate`] + /// + /// # Arguments + /// * `name` - The name of the value to retrieve. + /// + /// # Returns + /// A `Result` containing the deserialized value of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the value cannot be found, or deserialized into the given type + pub fn get_immediate(&mut self, name: &str) -> Result + where + T: serde::de::DeserializeOwned, + { + self.runtime + .get_value_immediate(Some(&self.module_context), name) + } + + /// Checks if a value in the module with the given name is callable as a JavaScript function. + /// + /// # Arguments + /// * `name` - The name of the value to check for callability. + /// + /// # Returns + /// `true` if the value is callable as a JavaScript function, `false` otherwise. + pub fn is_callable(&mut self, name: &str) -> bool { + let test = self.get::(name); + test.is_ok() + } + + /// Calls a function in the module with the given name and arguments and deserializes the result. + /// + /// See [`Runtime::call_function`] + /// + /// # Arguments + /// * `name` - The name of the function to call. + /// * `args` - The arguments to pass to the function. + /// + /// # Returns + /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the function cannot be called, if the function returns an error, + /// or if the function returns a value that cannot be deserialized into the given type + pub fn call(&mut self, name: &str, args: &impl serde::ser::Serialize) -> Result + where + T: serde::de::DeserializeOwned, + { + self.runtime + .call_function(Some(&self.module_context), name, args) + } + + /// Calls a function in the module with the given name and arguments and deserializes the result. + /// + /// See [`Runtime::call_function_async`] + /// + /// # Arguments + /// * `name` - The name of the function to call. + /// * `args` - The arguments to pass to the function. + /// + /// # Returns + /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the function cannot be called, if the function returns an error, + /// or if the function returns a value that cannot be deserialized into the given type + pub async fn call_async( + &mut self, + name: &str, + args: &impl serde::ser::Serialize, + ) -> Result { + self.runtime + .call_function_async(Some(&self.module_context), name, args) + .await + } + + /// Calls a function in the module with the given name and arguments and deserializes the result. + /// Does not await promises or the event loop. + /// + /// See [`Runtime::call_function_immediate`] + /// + /// # Arguments + /// * `name` - The name of the function to call. + /// * `args` - The arguments to pass to the function. + /// + /// # Returns + /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the function cannot be called, if the function returns an error, + /// or if the function returns a value that cannot be deserialized into the given type + pub fn call_immediate( + &mut self, + name: &str, + args: &impl serde::ser::Serialize, + ) -> Result { + self.runtime + .call_function_immediate(Some(&self.module_context), name, args) + } + + /// Calls a function using the module's runtime that was previously stored as a Function object + /// + /// See [`Runtime::call_stored_function`] + /// + /// # Arguments + /// * `function` - The Function to call. + /// * `args` - The arguments to pass to the function. + /// + /// # Returns + /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the function cannot be called, if the function returns an error, + /// or if the function returns a value that cannot be deserialized into the given type + pub fn call_stored( + &mut self, + function: &Function, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: serde::de::DeserializeOwned, + { + self.runtime + .call_stored_function(Some(&self.module_context), function, args) + } + + /// Calls a function using the module's runtime that was previously stored as a Function object + /// + /// See [`Runtime::call_stored_function_async`] + /// + /// # Arguments + /// * `function` - The Function to call. + /// * `args` - The arguments to pass to the function. + /// + /// # Returns + /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the function cannot be called, if the function returns an error, + /// or if the function returns a value that cannot be deserialized into the given type + pub async fn call_stored_async( + &mut self, + function: &Function, + args: &impl serde::ser::Serialize, + ) -> Result { + self.runtime + .call_stored_function_async(Some(&self.module_context), function, args) + .await + } + + /// Calls a function using the module's runtime that was previously stored as a Function object + /// + /// Does not await promises or the event loop. + /// + /// See [`Runtime::call_stored_function_immediate`] + /// + /// # Arguments + /// * `function` - The Function to call. + /// * `args` - The arguments to pass to the function. + /// + /// # Returns + /// A `Result` containing the deserialized result of type `T` on success or an `Error` on failure. + /// + /// # Errors + /// Will return an error if the function cannot be called, if the function returns an error, + /// or if the function returns a value that cannot be deserialized into the given type + pub fn call_stored_immediate( + &mut self, + function: &Function, + args: &impl serde::ser::Serialize, + ) -> Result { + self.runtime + .call_stored_function_immediate(Some(&self.module_context), function, args) + } + + /// Retrieves the names of the module's exports. + /// (Keys that are not valid UTF-8, may not work as intended due to encoding issues) + /// + /// # Returns + /// A `Vec` of `String` containing the names of the keys. + pub fn keys(&mut self) -> Vec { + let mut keys: Vec = Vec::new(); + if let Ok(namespace) = self + .runtime + .deno_runtime() + .get_module_namespace(self.module_context.id()) + { + let context = self.runtime.deno_runtime().main_context(); + let isolate = self.runtime.deno_runtime().v8_isolate(); + let pinned_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let mut scope = pinned_scope.init(); + let context_local = v8::Local::new(&scope, context); + let mut context_scope = v8::ContextScope::new(&mut scope, context_local); + + let global = v8::Local::new(&context_scope, namespace); + if let Some(keys_obj) = + global.get_property_names(&context_scope, GetPropertyNamesArgs::default()) + { + for i in 0..keys_obj.length() { + if let Ok(key_index) = deno_core::serde_v8::to_v8(&mut context_scope, i) { + if let Some(key_name_v8) = keys_obj.get(&context_scope, key_index) { + let name = key_name_v8.to_rust_string_lossy(&context_scope); + keys.push(name); + } + } + } + } + } + + keys + } +} + +#[cfg(test)] +mod test_runtime { + use super::*; + use crate::json_args; + + #[test] + fn test_call() { + let module = Module::new( + "test.js", + " + console.log('test'); + export const value = 3; + export function func() { return 4; } + ", + ); + + let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) + .expect("Could not create wrapper"); + let value: usize = module + .call("func", json_args!()) + .expect("Could not call function"); + assert_eq!(4, value); + } + + #[test] + fn test_get() { + let module = Module::new( + "test.js", + " + export const value = 3; + export function func() { return 4; } + ", + ); + + let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) + .expect("Could not create wrapper"); + let value: usize = module.get("value").expect("Could not get value"); + assert_eq!(3, value); + } + + #[test] + fn test_callable() { + let module = Module::new( + "test.js", + " + export const value = 3; + export function func() { return 4; } + ", + ); + + let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) + .expect("Could not create wrapper"); + + assert!(module.is_callable("func")); + assert!(!module.is_callable("value")); + } + + #[test] + fn test_keys() { + let module = Module::new( + "test.js", + " + export const value = 3; + export function func() { return 4; } + ", + ); + + let mut module = ModuleWrapper::new_from_module(&module, RuntimeOptions::default()) + .expect("Could not create wrapper"); + let mut keys = module.keys(); + assert_eq!(2, keys.len()); + assert_eq!("value", keys.pop().unwrap()); + assert_eq!("func", keys.pop().unwrap()); + } +} diff --git a/src/op_whitelist.rs b/src/op_whitelist.rs index 1d7a74f..8f6a78a 100644 --- a/src/op_whitelist.rs +++ b/src/op_whitelist.rs @@ -14,6 +14,7 @@ macro_rules! whitelist { } /// Get the global whitelist of ops. +#[must_use] pub fn get_whitelist() -> &'static OpWhitelist { &WHITELIST } @@ -44,6 +45,7 @@ whitelist!( op_print, op_resources, op_wasm_streaming_feed, + op_wasm_streaming_stream_feed, op_wasm_streaming_set_url, op_void_sync, op_error_async, @@ -132,6 +134,10 @@ whitelist!( op_timer_queue, op_timer_queue_system, op_timer_queue_immediate, + op_immediate_count, + op_immediate_ref_count, + op_immediate_set_has_outstanding, + op_immediate_has_ref_count, op_timer_cancel, op_timer_ref, op_timer_unref, @@ -238,11 +244,13 @@ whitelist!( pub struct OpWhitelist(&'static [OpSrc]); impl OpWhitelist { /// Create a new list of safe ops. + #[must_use] pub const fn new(ops: &'static [OpSrc]) -> Self { OpWhitelist(ops) } /// Check if the whitelist contains a specific op. + #[must_use] pub fn contains_op(&self, op: &str) -> bool { self.0.iter().any(|src| src.contains_op(op)) } @@ -269,7 +277,8 @@ pub struct OpSrc { pub stubs: &'static [&'static str], } impl OpSrc { - /// Create a new OpSrc. + /// Create a new `OpSrc`. + #[must_use] pub const fn new( src: &'static str, ops: &'static [&'static str], @@ -278,7 +287,8 @@ impl OpSrc { OpSrc { src, ops, stubs } } - /// Check if the OpSrc contains a specific op. + /// Check if the `OpSrc` contains a specific op. + #[must_use] pub fn contains_op(&self, op: &str) -> bool { self.ops.contains(&op) || self.stubs.contains(&op) } diff --git a/src/runtime.rs b/src/runtime.rs index a5be900..b41a628 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,1460 +1,1462 @@ -use std::{path::Path, rc::Rc, time::Duration}; - -use deno_core::PollEventLoopOptions; -use tokio_util::sync::CancellationToken; - -use crate::{ - async_bridge::{AsyncBridge, AsyncBridgeExt, TokioRuntime}, - inner_runtime::{InnerRuntime, RsAsyncFunction, RsFunction}, - js_value::Function, - Error, Module, ModuleHandle, -}; - -/// Represents the set of options accepted by the runtime constructor -pub use crate::inner_runtime::RuntimeOptions; - -/// For functions returning nothing. Acts as a placeholder for the return type -/// Should accept any type of value from javascript -/// -/// It is in fact an alias for [`crate::js_value::Value`] -/// Note: This used to be an alias for `serde_json::Value`, but was changed for performance reasons -pub type Undefined = crate::js_value::Value; - -/// A runtime instance that can be used to execute JavaScript code and interact with it. -/// Most runtime functions have 3 variants - blocking, async, and immediate -/// -/// For example: -/// - `call_function` will block until the function is resolved and the event loop is empty -/// - `call_function_async` will return a future that resolves when the function is resolved and the event loop is empty -/// - `call_function_immediate` will return the result immediately, without resolving promises or running the event loop -/// (See [`crate::js_value::Promise`]) -/// -/// Note: For multithreaded applications, you may need to call `init_platform` before creating a `Runtime` -/// (See [[`crate::init_platform`]) -pub struct Runtime { - inner: InnerRuntime, - tokio: AsyncBridge, -} - -impl Runtime { - /// Creates a new instance of the runtime with the provided options. - /// - /// # Arguments - /// * `options` - A `RuntimeOptions` struct that specifies the configuration options for the runtime. - /// - /// # Returns - /// A `Result` containing either the initialized runtime instance on success (`Ok`) or an error on failure (`Err`). - /// - /// # Example - /// ```rust - /// use rustyscript::{ json_args, Runtime, RuntimeOptions, Module }; - /// use std::time::Duration; - /// - /// # fn main() -> Result<(), rustyscript::Error> { - /// // Creates a runtime that will attempt to run function load() on start - /// // And which will time-out after 50ms - /// let mut runtime = Runtime::new(RuntimeOptions { - /// default_entrypoint: Some("load".to_string()), - /// timeout: Duration::from_millis(50), - /// ..Default::default() - /// })?; - /// - /// let module = Module::new("test.js", " - /// export const load = () => { - /// return 'Hello World!'; - /// } - /// "); - /// - /// let module_handle = runtime.load_module(&module)?; - /// let value: String = runtime.call_entrypoint(&module_handle, json_args!())?; - /// assert_eq!("Hello World!", value); - /// # Ok(()) - /// # } - /// ``` - /// - /// # Errors - /// Can fail if the tokio runtime cannot be created, - /// Or if the deno runtime initialization fails (usually issues with extensions) - /// - pub fn new(options: RuntimeOptions) -> Result { - let tokio = AsyncBridge::new(options.timeout)?; - let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?; - Ok(Self { inner, tokio }) - } - - /// Creates a new instance of the runtime with the provided options and a pre-configured tokio runtime. - /// See [`Runtime::new`] for more information. - /// - /// # Errors - /// Can fail if the deno runtime initialization fails (usually issues with extensions) - pub fn with_tokio_runtime( - options: RuntimeOptions, - tokio: Rc, - ) -> Result { - let tokio = AsyncBridge::with_tokio_runtime(options.timeout, tokio); - let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?; - Ok(Self { inner, tokio }) - } - - /// Creates a new instance of the runtime with the provided options and a borrowed tokio runtime handle. - /// See [`Runtime::new`] for more information. - /// - /// # Errors - /// Can fail if the deno runtime initialization fails (usually issues with extensions) - pub fn with_tokio_runtime_handle( - options: RuntimeOptions, - handle: tokio::runtime::Handle, - ) -> Result { - let tokio = AsyncBridge::with_runtime_handle(options.timeout, handle); - let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?; - Ok(Self { inner, tokio }) - } - - /// Access the underlying deno runtime instance directly - pub fn deno_runtime(&mut self) -> &mut deno_core::JsRuntime { - self.inner.deno_runtime() - } - - /// Access the underlying tokio runtime used for blocking operations - #[must_use] - pub fn tokio_runtime(&self) -> TokioRuntime { - self.tokio.tokio_runtime() - } - - /// Returns the timeout for the runtime - #[must_use] - pub fn timeout(&self) -> std::time::Duration { - self.tokio.timeout() - } - - /// Returns the heap exhausted token for the runtime - /// Used to detect when the runtime has run out of memory - #[must_use] - pub fn heap_exhausted_token(&self) -> CancellationToken { - self.tokio.heap_exhausted_token() - } - - /// Destroy the v8 runtime, releasing all resources - /// Then the internal tokio runtime will be returned - #[must_use] - pub fn into_tokio_runtime(self) -> TokioRuntime { - self.tokio.into_tokio_runtime() - } - - /// Set the current working directory for the runtime - /// This is used to resolve relative paths in the module loader - /// - /// The runtime will begin with the current working directory of the process - /// - /// # Errors - /// Can fail if the given path is not valid - pub fn set_current_dir(&mut self, path: impl AsRef) -> Result<&Path, Error> { - self.inner.set_current_dir(path) - } - - /// Get the current working directory for the runtime - /// This is used to resolve relative paths in the module loader - /// - /// The runtime will begin with the current working directory of the process - #[must_use] - pub fn current_dir(&self) -> &Path { - self.inner.current_dir() - } - - /// Advance the JS event loop by a single tick - /// See [`Runtime::block_on_event_loop`] for fully running the event loop - /// - /// Returns true if the event loop has pending work, or false if it has completed - /// - /// # Arguments - /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] - /// - /// # Errors - /// Can fail if a runtime error occurs during the event loop's execution - pub fn advance_event_loop(&mut self, options: PollEventLoopOptions) -> Result { - self.block_on(|runtime| async move { runtime.inner.advance_event_loop(options).await }) - } - - /// Advance the JS event loop by a single tick - /// See [`Runtime::await_event_loop`] for fully running the event loop - /// - /// Returns a future that resolves true if the event loop has pending work, or false if it - /// has completed - /// - /// # Arguments - /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] - /// - /// # Errors - /// Can fail if a runtime error occurs during the event loop's execution - pub async fn advance_event_loop_async( - &mut self, - options: PollEventLoopOptions, - ) -> Result { - self.inner.advance_event_loop(options).await - } - - /// Run the JS event loop to completion, or until a timeout is reached - /// Required when using the `_immediate` variants of functions - /// - /// # Arguments - /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] - /// * `timeout` - Optional timeout for the event loop - /// - /// # Errors - /// Can fail if a runtime error occurs during the event loop's execution - pub async fn await_event_loop( - &mut self, - options: PollEventLoopOptions, - timeout: Option, - ) -> Result<(), Error> { - self.inner.await_event_loop(options, timeout).await - } - - /// Run the JS event loop to completion, or until a timeout is reached - /// Required when using the `_immediate` variants of functions - /// - /// This is the blocking variant of [`Runtime::await_event_loop`] - /// - /// # Arguments - /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] - /// * `timeout` - Optional timeout for the event loop - /// - /// # Errors - /// Can fail if a runtime error occurs during the event loop's execution - pub fn block_on_event_loop( - &mut self, - options: deno_core::PollEventLoopOptions, - timeout: Option, - ) -> Result<(), Error> { - self.block_on(|runtime| async move { runtime.await_event_loop(options, timeout).await }) - } - - /// Remove and return a value from the state, if one exists - /// ```rust - /// use rustyscript::{ Runtime }; - /// - /// # fn main() -> Result<(), rustyscript::Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// runtime.put("test".to_string())?; - /// let value: String = runtime.take().unwrap(); - /// assert_eq!(value, "test"); - /// # Ok(()) - /// # } - /// ``` - pub fn take(&mut self) -> Option - where - T: 'static, - { - self.inner.take() - } - - /// Add a value to the state - /// Only one value of each type is stored - additional calls to `put` overwrite the old value - /// - /// # Errors - /// Can fail if the inner state cannot be borrowed mutably - /// - /// ```rust - /// use rustyscript::{ Runtime }; - /// - /// # fn main() -> Result<(), rustyscript::Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// runtime.put("test".to_string())?; - /// let value: String = runtime.take().unwrap(); - /// assert_eq!(value, "test"); - /// # Ok(()) - /// # } - /// ``` - pub fn put(&mut self, value: T) -> Result<(), Error> - where - T: 'static, - { - self.inner.put(value) - } - - /// Register a rust function to be callable from JS - /// - The [`crate::sync_callback`] macro can be used to simplify this process - /// - /// # Errors - /// Since this function borrows the state, it can fail if the state cannot be borrowed mutably - /// - /// ```rust - /// use rustyscript::{ Runtime, Module, serde_json::Value }; - /// - /// # fn main() -> Result<(), rustyscript::Error> { - /// let module = Module::new("test.js", " rustyscript.functions.foo(); "); - /// let mut runtime = Runtime::new(Default::default())?; - /// runtime.register_function("foo", |args| { - /// if let Some(value) = args.get(0) { - /// println!("called with: {}", value); - /// } - /// Ok(Value::Null) - /// })?; - /// - /// # Ok(()) - /// # } - /// ``` - pub fn register_function(&mut self, name: &str, callback: F) -> Result<(), Error> - where - F: RsFunction, - { - self.inner.register_function(name, callback) - } - - /// Register a non-blocking rust function to be callable from JS - /// - The [`crate::async_callback`] macro can be used to simplify this process - /// - /// # Errors - /// Since this function borrows the state, it can fail if the state cannot be borrowed mutably - /// - /// ```rust - /// use rustyscript::{ Runtime, Module, serde_json::Value, async_callback, Error }; - /// - /// # fn main() -> Result<(), rustyscript::Error> { - /// let module = Module::new("test.js", " rustyscript.async_functions.add(1, 2); "); - /// let mut runtime = Runtime::new(Default::default())?; - /// runtime.register_async_function("add", async_callback!( - /// |a: i64, b: i64| async move { - /// Ok::(a + b) - /// } - /// ))?; - /// - /// # Ok(()) - /// # } - /// ``` - pub fn register_async_function(&mut self, name: &str, callback: F) -> Result<(), Error> - where - F: RsAsyncFunction, - { - self.inner.register_async_function(name, callback) - } - - /// Evaluate a piece of non-ECMAScript-module JavaScript code - /// The expression is evaluated in the global context, so changes persist - /// - /// Blocks on promise resolution, and runs the event loop to completion - /// - /// Asynchronous code is supported, partially - /// - Top-level await is not supported - /// - The event loop will be run to completion after the expression is evaluated - /// - /// For top-level await support, use one of: - /// - `call_function_async` - /// - `call_stored_function_async` - /// - `load_module_async` - /// - `load_modules_async` - /// - /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`] - /// - /// # Arguments - /// * `expr` - A string representing the JavaScript expression to evaluate - /// - /// # Returns - /// A `Result` containing the deserialized result of the expression (`T`) - /// or an error (`Error`) if the expression cannot be evaluated or if the - /// result cannot be deserialized. - /// - /// # Errors - /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type - /// - /// # Example - /// ```rust - /// use rustyscript::{ Runtime, Error }; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// - /// let value: u32 = runtime.eval("2 + 2")?; - /// assert_eq!(4, value); - /// - /// let value: String = runtime.eval("new Promise(resolve => resolve('test'))")?; - /// assert_eq!("test", value); - /// - /// # Ok(()) - /// # } - /// ``` - pub fn eval(&mut self, expr: impl ToString) -> Result - where - T: serde::de::DeserializeOwned, - { - self.block_on(|runtime| async move { runtime.eval_async(expr).await }) - } - - /// Evaluate a piece of non-ECMAScript-module JavaScript code - /// The expression is evaluated in the global context, so changes persist - /// - /// Awaits promise resolution, and runs the event loop to completion - /// - /// Asynchronous code is supported, partially - /// - Top-level await is not supported - /// - The event loop will be run to completion after the expression is evaluated - /// - /// For top-level await support, use one of: - /// - `call_function_async` - /// - `call_stored_function_async` - /// - `load_module_async` - /// - `load_modules_async` - /// - /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`] - /// - /// # Arguments - /// * `expr` - A string representing the JavaScript expression to evaluate - /// - /// # Returns - /// A `Result` containing the deserialized result of the expression (`T`) - /// or an error (`Error`) if the expression cannot be evaluated or if the - /// result cannot be deserialized. - /// - /// # Errors - /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type - /// - /// # Example - /// For an example, see [`Runtime::eval`] - pub async fn eval_async(&mut self, expr: impl ToString) -> Result - where - T: serde::de::DeserializeOwned, - { - let result = self.inner.eval(expr.to_string()).await?; - let result = self.inner.resolve_with_event_loop(result).await?; - self.inner.decode_value(result) - } - - /// Evaluate a piece of non-ECMAScript-module JavaScript code - /// The expression is evaluated in the global context, so changes persist - /// - /// Does not await promise resolution, or run the event loop - /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] - /// The event loop should be run using [`Runtime::await_event_loop`] - /// - /// Note that this function needs to be async because calls to `setTimeout` must be evaluated from within an async runtime. - /// - /// Asynchronous code is supported, partially - /// - Top-level await is not supported - /// - /// For top-level await support, use one of: - /// - `call_function_async` - /// - `call_stored_function_async` - /// - `load_module_async` - /// - `load_modules_async` - /// - /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`] - /// - /// # Arguments - /// * `expr` - A string representing the JavaScript expression to evaluate - /// - /// # Returns - /// A `Result` containing the deserialized result of the expression (`T`) - /// or an error (`Error`) if the expression cannot be evaluated or if the - /// result cannot be deserialized. - /// - /// # Errors - /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type - /// - /// # Example - /// For an example, see [`Runtime::eval`] - pub async fn eval_immediate(&mut self, expr: impl ToString) -> Result - where - T: serde::de::DeserializeOwned, - { - let result = self.inner.eval(expr.to_string()).await?; - self.inner.decode_value(result) - } - - /// Calls a stored javascript function and deserializes its return value. - /// - /// Returns a future that resolves when: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// See [`Runtime::call_function`] for an example - /// - /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however. - /// - /// # Arguments - /// * `module_context` - Optional handle to a module providing global context for the function - /// * `function` - A The function object - /// * `args` - The arguments to pass to the function - /// - /// # Returns - /// A `Result` containing the deserialized result of the function call (`T`) - /// or an error (`Error`) if there are issues with calling the function, - /// or if the result cannot be deserialized. - /// - /// # Errors - /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type - pub async fn call_stored_function_async( - &mut self, - module_context: Option<&ModuleHandle>, - function: &Function, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: serde::de::DeserializeOwned, - { - let function = function.as_global(&mut self.deno_runtime().handle_scope()); - let result = self - .inner - .call_function_by_ref(module_context, &function, args)?; - let result = self.inner.resolve_with_event_loop(result).await?; - self.inner.decode_value(result) - } - - /// Calls a stored javascript function and deserializes its return value. - /// - /// Blocks until: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// See [`Runtime::call_function`] for an example - /// - /// # Arguments - /// * `module_context` - Optional handle to a module providing global context for the function - /// * `function` - A The function object - /// * `args` - The arguments to pass to the function - /// - /// # Returns - /// A `Result` containing the deserialized result of the function call (`T`) - /// or an error (`Error`) if there are issues with calling the function, - /// or if the result cannot be deserialized. - /// - /// # Errors - /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type - pub fn call_stored_function( - &mut self, - module_context: Option<&ModuleHandle>, - function: &Function, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - self.block_on(|runtime| async move { - runtime - .call_stored_function_async(module_context, function, args) - .await - }) - } - - /// Calls a stored javascript function and deserializes its return value. - /// - /// Will not attempt to resolve promises, or run the event loop - /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] - /// The event loop should be run using [`Runtime::await_event_loop`] - /// - /// See [`Runtime::call_function`] for an example - /// - /// # Arguments - /// * `module_context` - Optional handle to a module providing global context for the function - /// * `function` - A The function object - /// * `args` - The arguments to pass to the function - /// - /// # Returns - /// A `Result` containing the deserialized result of the function call (`T`) - /// or an error (`Error`) if there are issues with calling the function, - /// or if the result cannot be deserialized. - /// - /// # Errors - /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type - pub fn call_stored_function_immediate( - &mut self, - module_context: Option<&ModuleHandle>, - function: &Function, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - let function = function.as_global(&mut self.deno_runtime().handle_scope()); - let result = self - .inner - .call_function_by_ref(module_context, &function, args)?; - self.inner.decode_value(result) - } - - /// Calls a javascript function within the Deno runtime by its name and deserializes its return value. - /// - /// Returns a future that resolves when: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however. - /// - /// See [`Runtime::call_function`] for an example - /// - /// # Arguments - /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used - /// * `name` - A string representing the name of the javascript function to call. - /// * `args` - The arguments to pass to the function - /// - /// # Returns - /// A `Result` containing the deserialized result of the function call (`T`) - /// or an error (`Error`) if the function cannot be found, if there are issues with - /// calling the function, or if the result cannot be deserialized. - /// - /// # Errors - /// Fails if the function cannot be found, if there are issues with calling the function, - /// Or if the result cannot be deserialized into the requested type - pub async fn call_function_async( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - let function = self.inner.get_function_by_name(module_context, name)?; - let result = self - .inner - .call_function_by_ref(module_context, &function, args)?; - let result = self.inner.resolve_with_event_loop(result).await?; - self.inner.decode_value(result) - } - - /// Calls a javascript function within the Deno runtime by its name and deserializes its return value. - /// - /// Blocks until: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// # Arguments - /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used - /// * `name` - A string representing the name of the javascript function to call. - /// * `args` - The arguments to pass to the function - /// - /// # Returns - /// A `Result` containing the deserialized result of the function call (`T`) - /// or an error (`Error`) if the function cannot be found, if there are issues with - /// calling the function, or if the result cannot be deserialized. - /// - /// # Errors - /// Fails if the function cannot be found, if there are issues with calling the function, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Example - /// - /// ```rust - /// use rustyscript::{ json_args, Runtime, Module, Error }; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("/path/to/module.js", "export function f() { return 2; };"); - /// let module = runtime.load_module(&module)?; - /// let value: usize = runtime.call_function(Some(&module), "f", json_args!())?; - /// # Ok(()) - /// # } - /// ``` - pub fn call_function( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - self.block_on(|runtime| async move { - runtime - .call_function_async(module_context, name, args) - .await - }) - } - - /// Calls a javascript function within the Deno runtime by its name and deserializes its return value. - /// - /// Will not attempt to resolve promises, or run the event loop - /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] - /// The event loop should be run using [`Runtime::await_event_loop`] - /// - /// # Arguments - /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used - /// * `name` - A string representing the name of the javascript function to call. - /// * `args` - The arguments to pass to the function - /// - /// # Returns - /// A `Result` containing the deserialized result of the function call (`T`) - /// or an error (`Error`) if the function cannot be found, if there are issues with - /// calling the function, or if the result cannot be deserialized. - /// - /// # Errors - /// Fails if the function cannot be found, if there are issues with calling the function, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Example - /// - /// ```rust - /// use rustyscript::{ json_args, Runtime, Module, Error }; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("/path/to/module.js", "export function f() { return 2; };"); - /// let module = runtime.load_module(&module)?; - /// let value: usize = runtime.call_function_immediate(Some(&module), "f", json_args!())?; - /// # Ok(()) - /// # } - /// ``` - pub fn call_function_immediate( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - let function = self.inner.get_function_by_name(module_context, name)?; - let result = self - .inner - .call_function_by_ref(module_context, &function, args)?; - self.inner.decode_value(result) - } - - /// Get a value from a runtime instance - /// - /// Blocks until: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// # Arguments - /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used - /// * `name` - A string representing the name of the value to find - /// - /// # Returns - /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Errors - /// Can fail if the value cannot be found, or if the result cannot be deserialized. - /// - /// # Example - /// - /// ```rust - /// use rustyscript::{ Runtime, Module, Error }; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("/path/to/module.js", "globalThis.my_value = 2;"); - /// let module = runtime.load_module(&module)?; - /// let value: usize = runtime.get_value(Some(&module), "my_value")?; - /// # Ok(()) - /// # } - /// ``` - pub fn get_value( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - ) -> Result - where - T: serde::de::DeserializeOwned, - { - self.block_on(|runtime| async move { runtime.get_value_async(module_context, name).await }) - } - - /// Get a value from a runtime instance - /// - /// Returns a future that resolves when: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// See [`Runtime::get_value`] for an example - /// - /// # Arguments - /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used - /// * `name` - A string representing the name of the value to find - /// - /// # Returns - /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Errors - /// Can fail if the value cannot be found, or if the result cannot be deserialized. - pub async fn get_value_async( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - ) -> Result - where - T: serde::de::DeserializeOwned, - { - let result = self.inner.get_value_ref(module_context, name)?; - let result = self.inner.resolve_with_event_loop(result).await?; - self.inner.decode_value(result) - } - - /// Get a value from a runtime instance - /// - /// Will not attempt to resolve promises, or run the event loop - /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] - /// The event loop should be run using [`Runtime::await_event_loop`] - /// - /// # Arguments - /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used - /// * `name` - A string representing the name of the value to find - /// - /// # Returns - /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Errors - /// Can fail if the value cannot be found, or if the result cannot be deserialized. - /// - /// # Example - /// - /// ```rust - /// use rustyscript::{ Runtime, Module, Error }; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("/path/to/module.js", "globalThis.my_value = 2;"); - /// let module = runtime.load_module(&module)?; - /// let value: usize = runtime.get_value_immediate(Some(&module), "my_value")?; - /// # Ok(()) - /// # } - /// ``` - pub fn get_value_immediate( - &mut self, - module_context: Option<&ModuleHandle>, - name: &str, - ) -> Result - where - T: serde::de::DeserializeOwned, - { - let result = self.inner.get_value_ref(module_context, name)?; - self.inner.decode_value(result) - } - - /// Executes the given module, and returns a handle allowing you to extract values - /// and call functions - /// - /// Blocks until the module has been executed AND the event loop has fully resolved - /// See [`Runtime::load_module_async`] for a non-blocking variant, or use with async - /// background tasks - /// - /// # Arguments - /// * `module` - A `Module` object containing the module's filename and contents. - /// - /// # Returns - /// A `Result` containing a handle for the loaded module - /// or an error (`Error`) if there are issues with loading or executing the module - /// - /// # Errors - /// Can fail if the module cannot be loaded, or execution fails - /// - /// # Example - /// - /// ```rust - /// // Create a module with filename and contents - /// use rustyscript::{Runtime, Module, Error}; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("test.js", "export default () => 'test'"); - /// runtime.load_module(&module); - /// # Ok(()) - /// # } - /// ``` - pub fn load_module(&mut self, module: &Module) -> Result { - self.block_on(|runtime| async move { - let handle = runtime.load_module_async(module).await; - runtime - .await_event_loop(PollEventLoopOptions::default(), None) - .await?; - handle - }) - } - - /// Executes the given module, and returns a handle allowing you to extract values - /// and call functions - /// - /// Returns a future that resolves to the handle for the loaded module - /// Makes no attempt to fully resolve the event loop - call [`Runtime::await_event_loop`] - /// to resolve background tasks and async listeners - /// - /// # Arguments - /// * `module` - A `Module` object containing the module's filename and contents. - /// - /// # Returns - /// A `Result` containing a handle for the loaded module - /// or an error (`Error`) if there are issues with loading or executing the module - /// - /// # Errors - /// Can fail if the module cannot be loaded, or execution fails - /// - /// See [`Runtime::load_module`] for an example - pub async fn load_module_async(&mut self, module: &Module) -> Result { - self.inner.load_modules(None, vec![module]).await - } - - /// Executes the given module, and returns a handle allowing you to extract values - /// and call functions. - /// - /// Blocks until all modules have been executed AND the event loop has fully resolved - /// See [`Runtime::load_module_async`] for a non-blocking variant, or use with async - /// background tasks - /// - /// This will load 'module' as the main module, and the others as side-modules. - /// Only one main module can be loaded per runtime - /// - /// # Arguments - /// * `module` - A `Module` object containing the module's filename and contents. - /// * `side_modules` - A set of additional modules to be loaded into memory for use - /// - /// # Returns - /// A `Result` containing a handle for the loaded module - /// or an error (`Error`) if there are issues with loading or executing the module - /// - /// # Errors - /// Can fail if the module cannot be loaded, or execution fails - /// - /// # Example - /// - /// ```rust - /// // Create a module with filename and contents - /// use rustyscript::{Runtime, Module, Error}; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("test.js", "export default () => 'test'"); - /// runtime.load_modules(&module, vec![]); - /// # Ok(()) - /// # } - /// ``` - pub fn load_modules( - &mut self, - module: &Module, - side_modules: Vec<&Module>, - ) -> Result { - self.block_on(move |runtime| async move { - let handle = runtime.load_modules_async(module, side_modules).await; - runtime - .await_event_loop(PollEventLoopOptions::default(), None) - .await?; - handle - }) - } - - /// Executes the given module, and returns a handle allowing you to extract values - /// and call functions. - /// - /// Returns a future that resolves to the handle for the loaded module - /// Makes no attempt to resolve the event loop - call [`Runtime::await_event_loop`] to - /// resolve background tasks and async listeners - /// - /// This will load 'module' as the main module, and the others as side-modules. - /// Only one main module can be loaded per runtime - /// - /// See [`Runtime::load_modules`] for an example - /// - /// # Arguments - /// * `module` - A `Module` object containing the module's filename and contents. - /// * `side_modules` - A set of additional modules to be loaded into memory for use - /// - /// # Returns - /// A `Result` containing a handle for the loaded main module, or the last side-module - /// or an error (`Error`) if there are issues with loading or executing the modules - /// - /// # Errors - /// Can fail if the modules cannot be loaded, or execution fails - pub async fn load_modules_async( - &mut self, - module: &Module, - side_modules: Vec<&Module>, - ) -> Result { - self.inner.load_modules(Some(module), side_modules).await - } - - /// Executes the entrypoint function of a module within the Deno runtime. - /// - /// Blocks until: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// # Arguments - /// * `module_context` - A handle returned by loading a module into the runtime - /// - /// # Returns - /// A `Result` containing the deserialized result of the entrypoint execution (`T`) - /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, - /// or the result cannot be deserialized. - /// - /// # Errors - /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Example - /// - /// ```rust - /// use rustyscript::{json_args, Runtime, Module, Error}; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("test.js", "export default () => 'test'"); - /// let module = runtime.load_module(&module)?; - /// - /// // Run the entrypoint and handle the result - /// let value: String = runtime.call_entrypoint(&module, json_args!())?; - /// # Ok(()) - /// # } - /// ``` - pub fn call_entrypoint( - &mut self, - module_context: &ModuleHandle, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - self.block_on( - |runtime| async move { runtime.call_entrypoint_async(module_context, args).await }, - ) - } - - /// Executes the entrypoint function of a module within the Deno runtime. - /// - /// Returns a future that resolves when: - /// - The event loop is resolved, and - /// - If the value is a promise, the promise is resolved - /// - /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however. - /// - /// See [`Runtime::call_entrypoint`] for an example - /// - /// # Arguments - /// * `module_context` - A handle returned by loading a module into the runtime - /// - /// # Returns - /// A `Result` containing the deserialized result of the entrypoint execution (`T`) - /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, - /// or the result cannot be deserialized. - /// - /// # Errors - /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, - /// Or if the result cannot be deserialized into the requested type - pub async fn call_entrypoint_async( - &mut self, - module_context: &ModuleHandle, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - if let Some(entrypoint) = module_context.entrypoint() { - let result = self - .inner - .call_function_by_ref(Some(module_context), entrypoint, args)?; - let result = self.inner.resolve_with_event_loop(result).await?; - self.inner.decode_value(result) - } else { - Err(Error::MissingEntrypoint(module_context.module().clone())) - } - } - - /// Executes the entrypoint function of a module within the Deno runtime. - /// - /// Will not attempt to resolve promises, or run the event loop - /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] - /// The event loop should be run using [`Runtime::await_event_loop`] - /// - /// # Arguments - /// * `module_context` - A handle returned by loading a module into the runtime - /// - /// # Returns - /// A `Result` containing the deserialized result of the entrypoint execution (`T`) - /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, - /// or the result cannot be deserialized. - /// - /// # Errors - /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Example - /// - /// ```rust - /// use rustyscript::{json_args, Runtime, Module, Error}; - /// - /// # fn main() -> Result<(), Error> { - /// let mut runtime = Runtime::new(Default::default())?; - /// let module = Module::new("test.js", "export default () => 'test'"); - /// let module = runtime.load_module(&module)?; - /// - /// // Run the entrypoint and handle the result - /// let value: String = runtime.call_entrypoint_immediate(&module, json_args!())?; - /// # Ok(()) - /// # } - /// ``` - pub fn call_entrypoint_immediate( - &mut self, - module_context: &ModuleHandle, - args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - if let Some(entrypoint) = module_context.entrypoint() { - let result = self.block_on(|runtime| async move { - runtime - .inner - .call_function_by_ref(Some(module_context), entrypoint, args) - })?; - self.inner.decode_value(result) - } else { - Err(Error::MissingEntrypoint(module_context.module().clone())) - } - } - - /// Loads a module into a new runtime, executes the entry function and returns the - /// result of the module's execution, deserialized into the specified Rust type (`T`). - /// - /// # Arguments - /// * `module` - A `Module` object containing the module's filename and contents. - /// * `side_modules` - A set of additional modules to be loaded into memory for use - /// * `runtime_options` - Options for the creation of the runtime - /// * `entrypoint_args` - Arguments to pass to the entrypoint function - /// - /// # Returns - /// A `Result` containing the deserialized result of the entrypoint execution (`T`) - /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, - /// or the result cannot be deserialized. - /// - /// # Errors - /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, - /// Or if the result cannot be deserialized into the requested type - /// - /// # Example - /// - /// ```rust - /// // Create a module with filename and contents - /// use rustyscript::{json_args, Runtime, Module, Error}; - /// - /// # fn main() -> Result<(), Error> { - /// let module = Module::new("test.js", "export default () => 2"); - /// let value: usize = Runtime::execute_module(&module, vec![], Default::default(), json_args!())?; - /// # Ok(()) - /// # } - /// ``` - pub fn execute_module( - module: &Module, - side_modules: Vec<&Module>, - runtime_options: RuntimeOptions, - entrypoint_args: &impl serde::ser::Serialize, - ) -> Result - where - T: deno_core::serde::de::DeserializeOwned, - { - let mut runtime = Runtime::new(runtime_options)?; - let module = runtime.load_modules(module, side_modules)?; - let value: T = runtime.call_entrypoint(&module, entrypoint_args)?; - Ok(value) - } -} - -impl AsyncBridgeExt for Runtime { - fn bridge(&self) -> &AsyncBridge { - &self.tokio - } -} - -#[cfg(test)] -mod test_runtime { - use crate::json_args; - use std::time::Duration; - - use super::*; - use deno_core::extension; - - #[test] - fn test_new() { - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - - extension!(test_extension); - Runtime::new(RuntimeOptions { - extensions: vec![test_extension::init()], - ..Default::default() - }) - .expect("Could not create runtime with extensions"); - } - - #[test] - fn test_get_value() { - let module = Module::new( - "test.js", - " - globalThis.a = 2; - export const b = 'test'; - export const fnc = null; - ", - ); - - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module = runtime - .load_modules(&module, vec![]) - .expect("Could not load module"); - - assert_eq!( - 2, - runtime - .get_value::(Some(&module), "a") - .expect("Could not find global") - ); - assert_eq!( - "test", - runtime - .get_value::(Some(&module), "b") - .expect("Could not find export") - ); - runtime - .get_value::(Some(&module), "c") - .expect_err("Could not detect null"); - runtime - .get_value::(Some(&module), "d") - .expect_err("Could not detect undeclared"); - } - - #[test] - fn test_load_module() { - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module = Module::new( - "test.js", - " - export default () => 2; - ", - ); - let module = runtime - .load_modules(&module, vec![]) - .expect("Could not load module"); - assert_ne!(0, module.id()); - - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module1 = Module::new( - "importme.js", - " - export const value = 2; - ", - ); - let module2 = Module::new( - "test.js", - " - import { value } from './importme.js'; - rustyscript.register_entrypoint(() => value); - ", - ); - runtime - .load_module(&module1) - .expect("Could not load modules"); - let module = runtime - .load_module(&module2) - .expect("Could not load modules"); - let value: usize = runtime - .call_entrypoint(&module, json_args!()) - .expect("Could not call exported fn"); - assert_eq!(2, value); - - let mut runtime = Runtime::new(RuntimeOptions { - timeout: Duration::from_millis(50), - ..Default::default() - }) - .expect("Could not create the runtime"); - let module = Module::new( - "test.js", - " - await new Promise(r => setTimeout(r, 2000)); - ", - ); - runtime - .load_modules(&module, vec![]) - .expect_err("Did not interupt after timeout"); - } - - #[test] - fn test_load_modules() { - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module = Module::new( - "test.js", - " - rustyscript.register_entrypoint(() => 2); - ", - ); - let module = runtime - .load_modules(&module, vec![]) - .expect("Could not load module"); - assert_ne!(0, module.id()); - - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module1 = Module::new( - "importme.js", - " - export const value = 2; - ", - ); - let module2 = Module::new( - "test.js", - " - import { value } from './importme.js'; - rustyscript.register_entrypoint(() => value); - ", - ); - let module = runtime - .load_modules(&module2, vec![&module1]) - .expect("Could not load modules"); - let value: usize = runtime - .call_entrypoint(&module, json_args!()) - .expect("Could not call exported fn"); - assert_eq!(2, value); - - let mut runtime = Runtime::new(RuntimeOptions { - timeout: Duration::from_millis(50), - ..Default::default() - }) - .expect("Could not create the runtime"); - let module = Module::new( - "test.js", - " - await new Promise(r => setTimeout(r, 5000)); - ", - ); - runtime - .load_modules(&module, vec![]) - .expect_err("Did not interupt after timeout"); - } - - #[test] - fn test_call_entrypoint() { - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module = Module::new( - "test.js", - " - rustyscript.register_entrypoint(() => 2); - ", - ); - let module = runtime - .load_modules(&module, vec![]) - .expect("Could not load module"); - let value: usize = runtime - .call_entrypoint(&module, json_args!()) - .expect("Could not call registered fn"); - assert_eq!(2, value); - - let mut runtime = Runtime::new(RuntimeOptions { - default_entrypoint: Some("load".to_string()), - ..Default::default() - }) - .expect("Could not create the runtime"); - let module = Module::new( - "test.js", - " - export const load = () => 2; - ", - ); - let module = runtime - .load_modules(&module, vec![]) - .expect("Could not load module"); - let value: usize = runtime - .call_entrypoint(&module, json_args!()) - .expect("Could not call exported fn"); - assert_eq!(2, value); - - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module = Module::new( - "test.js", - " - export const load = () => 2; - ", - ); - let module = runtime - .load_modules(&module, vec![]) - .expect("Could not load module"); - runtime - .call_entrypoint::(&module, json_args!()) - .expect_err("Did not detect no entrypoint"); - } - - #[test] - fn test_execute_module() { - let module = Module::new( - "test.js", - " - rustyscript.register_entrypoint(() => 2); - ", - ); - let value: usize = - Runtime::execute_module(&module, vec![], RuntimeOptions::default(), json_args!()) - .expect("Could not exec module"); - assert_eq!(2, value); - - let module = Module::new( - "test.js", - " - function load() { return 2; } - ", - ); - Runtime::execute_module::( - &module, - vec![], - RuntimeOptions::default(), - json_args!(), - ) - .expect_err("Could not detect no entrypoint"); - } - - #[test] - fn call_function() { - let module = Module::new( - "test.js", - " - globalThis.fna = (i) => i; - export function fnb() { return 'test'; } - export const fnc = 2; - export const fne = () => {}; - ", - ); - - let mut runtime = - Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); - let module = runtime - .load_modules(&module, vec![]) - .expect("Could not load module"); - - let result: usize = runtime - .call_function(Some(&module), "fna", json_args!(2)) - .expect("Could not call global"); - assert_eq!(2, result); - - let result: String = runtime - .call_function(Some(&module), "fnb", json_args!()) - .expect("Could not call export"); - assert_eq!("test", result); - - runtime - .call_function::(Some(&module), "fnc", json_args!()) - .expect_err("Did not detect non-function"); - runtime - .call_function::(Some(&module), "fnd", json_args!()) - .expect_err("Did not detect undefined"); - runtime - .call_function::(Some(&module), "fne", json_args!()) - .expect("Did not allow undefined return"); - } - - #[test] - fn test_heap_exhaustion_handled() { - let mut runtime = Runtime::new(RuntimeOptions { - max_heap_size: Some(100 * 1024 * 1024), - ..Default::default() - }) - .expect("Could not create the runtime"); - let module = Module::new( - "test.js", - "const largeArray = new Array(40 * 1024 * 1024).fill('a');", - ); - runtime - .load_modules(&module, vec![]) - .expect_err("Did not detect heap exhaustion"); - } -} +use std::{path::Path, rc::Rc, time::Duration}; + +use deno_core::PollEventLoopOptions; +use tokio_util::sync::CancellationToken; + +use crate::{ + async_bridge::{AsyncBridge, AsyncBridgeExt, TokioRuntime}, + inner_runtime::{InnerRuntime, RsAsyncFunction, RsFunction}, + js_value::Function, + Error, Module, ModuleHandle, +}; + +/// Represents the set of options accepted by the runtime constructor +pub use crate::inner_runtime::RuntimeOptions; + +/// For functions returning nothing. Acts as a placeholder for the return type +/// Should accept any type of value from javascript +/// +/// It is in fact an alias for [`crate::js_value::Value`] +/// Note: This used to be an alias for `serde_json::Value`, but was changed for performance reasons +pub type Undefined = crate::js_value::Value; + +/// A runtime instance that can be used to execute JavaScript code and interact with it. +/// Most runtime functions have 3 variants - blocking, async, and immediate +/// +/// For example: +/// - `call_function` will block until the function is resolved and the event loop is empty +/// - `call_function_async` will return a future that resolves when the function is resolved and the event loop is empty +/// - `call_function_immediate` will return the result immediately, without resolving promises or running the event loop +/// (See [`crate::js_value::Promise`]) +/// +/// Note: For multithreaded applications, you may need to call `init_platform` before creating a `Runtime` +/// (See [[`crate::init_platform`]) +pub struct Runtime { + inner: InnerRuntime, + tokio: AsyncBridge, +} + +impl Runtime { + /// Creates a new instance of the runtime with the provided options. + /// + /// # Arguments + /// * `options` - A `RuntimeOptions` struct that specifies the configuration options for the runtime. + /// + /// # Returns + /// A `Result` containing either the initialized runtime instance on success (`Ok`) or an error on failure (`Err`). + /// + /// # Example + /// ```no_run + /// use rustyscript::{ json_args, Runtime, RuntimeOptions, Module }; + /// use std::time::Duration; + /// + /// # fn main() -> Result<(), rustyscript::Error> { + /// // Creates a runtime that will attempt to run function load() on start + /// // And which will time-out after 50ms + /// let mut runtime = Runtime::new(RuntimeOptions { + /// default_entrypoint: Some("load".to_string()), + /// timeout: Duration::from_millis(50), + /// ..Default::default() + /// })?; + /// + /// let module = Module::new("test.js", " + /// export const load = () => { + /// return 'Hello World!'; + /// } + /// "); + /// + /// let module_handle = runtime.load_module(&module)?; + /// let value: String = runtime.call_entrypoint(&module_handle, json_args!())?; + /// assert_eq!("Hello World!", value); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// Can fail if the tokio runtime cannot be created, + /// Or if the deno runtime initialization fails (usually issues with extensions) + /// + pub fn new(options: RuntimeOptions) -> Result { + let tokio = AsyncBridge::new(options.timeout)?; + let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?; + Ok(Self { inner, tokio }) + } + + /// Creates a new instance of the runtime with the provided options and a pre-configured tokio runtime. + /// See [`Runtime::new`] for more information. + /// + /// # Errors + /// Can fail if the deno runtime initialization fails (usually issues with extensions) + pub fn with_tokio_runtime( + options: RuntimeOptions, + tokio: Rc, + ) -> Result { + let tokio = AsyncBridge::with_tokio_runtime(options.timeout, tokio); + let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?; + Ok(Self { inner, tokio }) + } + + /// Creates a new instance of the runtime with the provided options and a borrowed tokio runtime handle. + /// See [`Runtime::new`] for more information. + /// + /// # Errors + /// Can fail if the deno runtime initialization fails (usually issues with extensions) + pub fn with_tokio_runtime_handle( + options: RuntimeOptions, + handle: tokio::runtime::Handle, + ) -> Result { + let tokio = AsyncBridge::with_runtime_handle(options.timeout, handle); + let inner = InnerRuntime::new(options, tokio.heap_exhausted_token())?; + Ok(Self { inner, tokio }) + } + + /// Access the underlying deno runtime instance directly + pub fn deno_runtime(&mut self) -> &mut deno_core::JsRuntime { + self.inner.deno_runtime() + } + + /// Access the underlying tokio runtime used for blocking operations + #[must_use] + pub fn tokio_runtime(&self) -> TokioRuntime { + self.tokio.tokio_runtime() + } + + /// Returns the timeout for the runtime + #[must_use] + pub fn timeout(&self) -> std::time::Duration { + self.tokio.timeout() + } + + /// Returns the heap exhausted token for the runtime + /// Used to detect when the runtime has run out of memory + #[must_use] + pub fn heap_exhausted_token(&self) -> CancellationToken { + self.tokio.heap_exhausted_token() + } + + /// Destroy the v8 runtime, releasing all resources + /// Then the internal tokio runtime will be returned + #[must_use] + pub fn into_tokio_runtime(self) -> TokioRuntime { + self.tokio.into_tokio_runtime() + } + + /// Set the current working directory for the runtime + /// This is used to resolve relative paths in the module loader + /// + /// The runtime will begin with the current working directory of the process + /// + /// # Errors + /// Can fail if the given path is not valid + pub fn set_current_dir(&mut self, path: impl AsRef) -> Result<&Path, Error> { + self.inner.set_current_dir(path) + } + + /// Get the current working directory for the runtime + /// This is used to resolve relative paths in the module loader + /// + /// The runtime will begin with the current working directory of the process + #[must_use] + pub fn current_dir(&self) -> &Path { + self.inner.current_dir() + } + + /// Advance the JS event loop by a single tick + /// See [`Runtime::block_on_event_loop`] for fully running the event loop + /// + /// Returns true if the event loop has pending work, or false if it has completed + /// + /// # Arguments + /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] + /// + /// # Errors + /// Can fail if a runtime error occurs during the event loop's execution + pub fn advance_event_loop(&mut self, options: PollEventLoopOptions) -> Result { + self.block_on(|runtime| async move { runtime.inner.advance_event_loop(options).await }) + } + + /// Advance the JS event loop by a single tick + /// See [`Runtime::await_event_loop`] for fully running the event loop + /// + /// Returns a future that resolves true if the event loop has pending work, or false if it + /// has completed + /// + /// # Arguments + /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] + /// + /// # Errors + /// Can fail if a runtime error occurs during the event loop's execution + pub async fn advance_event_loop_async( + &mut self, + options: PollEventLoopOptions, + ) -> Result { + self.inner.advance_event_loop(options).await + } + + /// Run the JS event loop to completion, or until a timeout is reached + /// Required when using the `_immediate` variants of functions + /// + /// # Arguments + /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] + /// * `timeout` - Optional timeout for the event loop + /// + /// # Errors + /// Can fail if a runtime error occurs during the event loop's execution + pub async fn await_event_loop( + &mut self, + options: PollEventLoopOptions, + timeout: Option, + ) -> Result<(), Error> { + self.inner.await_event_loop(options, timeout).await + } + + /// Run the JS event loop to completion, or until a timeout is reached + /// Required when using the `_immediate` variants of functions + /// + /// This is the blocking variant of [`Runtime::await_event_loop`] + /// + /// # Arguments + /// * `options` - Options for the event loop polling, see [`deno_core::PollEventLoopOptions`] + /// * `timeout` - Optional timeout for the event loop + /// + /// # Errors + /// Can fail if a runtime error occurs during the event loop's execution + pub fn block_on_event_loop( + &mut self, + options: deno_core::PollEventLoopOptions, + timeout: Option, + ) -> Result<(), Error> { + self.block_on(|runtime| async move { runtime.await_event_loop(options, timeout).await }) + } + + /// Remove and return a value from the state, if one exists + /// ```no_run + /// use rustyscript::{ Runtime }; + /// + /// # fn main() -> Result<(), rustyscript::Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// runtime.put("test".to_string())?; + /// let value: String = runtime.take().unwrap(); + /// assert_eq!(value, "test"); + /// # Ok(()) + /// # } + /// ``` + pub fn take(&mut self) -> Option + where + T: 'static, + { + self.inner.take() + } + + /// Add a value to the state + /// Only one value of each type is stored - additional calls to `put` overwrite the old value + /// + /// # Errors + /// Can fail if the inner state cannot be borrowed mutably + /// + /// ```no_run + /// use rustyscript::{ Runtime }; + /// + /// # fn main() -> Result<(), rustyscript::Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// runtime.put("test".to_string())?; + /// let value: String = runtime.take().unwrap(); + /// assert_eq!(value, "test"); + /// # Ok(()) + /// # } + /// ``` + pub fn put(&mut self, value: T) -> Result<(), Error> + where + T: 'static, + { + self.inner.put(value) + } + + /// Register a rust function to be callable from JS + /// - The [`crate::sync_callback`] macro can be used to simplify this process + /// + /// # Errors + /// Since this function borrows the state, it can fail if the state cannot be borrowed mutably + /// + /// ```no_run + /// use rustyscript::{ Runtime, Module, serde_json::Value }; + /// + /// # fn main() -> Result<(), rustyscript::Error> { + /// let module = Module::new("test.js", " rustyscript.functions.foo(); "); + /// let mut runtime = Runtime::new(Default::default())?; + /// runtime.register_function("foo", |args| { + /// if let Some(value) = args.get(0) { + /// println!("called with: {}", value); + /// } + /// Ok(Value::Null) + /// })?; + /// + /// # Ok(()) + /// # } + /// ``` + pub fn register_function(&mut self, name: &str, callback: F) -> Result<(), Error> + where + F: RsFunction, + { + self.inner.register_function(name, callback) + } + + /// Register a non-blocking rust function to be callable from JS + /// - The [`crate::async_callback`] macro can be used to simplify this process + /// + /// # Errors + /// Since this function borrows the state, it can fail if the state cannot be borrowed mutably + /// + /// ```no_run + /// use rustyscript::{ Runtime, Module, serde_json::Value, async_callback, Error }; + /// + /// # fn main() -> Result<(), rustyscript::Error> { + /// let module = Module::new("test.js", " rustyscript.async_functions.add(1, 2); "); + /// let mut runtime = Runtime::new(Default::default())?; + /// runtime.register_async_function("add", async_callback!( + /// |a: i64, b: i64| async move { + /// Ok::(a + b) + /// } + /// ))?; + /// + /// # Ok(()) + /// # } + /// ``` + pub fn register_async_function(&mut self, name: &str, callback: F) -> Result<(), Error> + where + F: RsAsyncFunction, + { + self.inner.register_async_function(name, callback) + } + + /// Evaluate a piece of non-ECMAScript-module JavaScript code + /// The expression is evaluated in the global context, so changes persist + /// + /// Blocks on promise resolution, and runs the event loop to completion + /// + /// Asynchronous code is supported, partially + /// - Top-level await is not supported + /// - The event loop will be run to completion after the expression is evaluated + /// + /// For top-level await support, use one of: + /// - `call_function_async` + /// - `call_stored_function_async` + /// - `load_module_async` + /// - `load_modules_async` + /// + /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`] + /// + /// # Arguments + /// * `expr` - A string representing the JavaScript expression to evaluate + /// + /// # Returns + /// A `Result` containing the deserialized result of the expression (`T`) + /// or an error (`Error`) if the expression cannot be evaluated or if the + /// result cannot be deserialized. + /// + /// # Errors + /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type + /// + /// # Example + /// ```no_run + /// use rustyscript::{ Runtime, Error }; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// + /// let value: u32 = runtime.eval("2 + 2")?; + /// assert_eq!(4, value); + /// + /// let value: String = runtime.eval("new Promise(resolve => resolve('test'))")?; + /// assert_eq!("test", value); + /// + /// # Ok(()) + /// # } + /// ``` + pub fn eval(&mut self, expr: impl ToString) -> Result + where + T: serde::de::DeserializeOwned, + { + self.block_on(|runtime| async move { runtime.eval_async(expr).await }) + } + + /// Evaluate a piece of non-ECMAScript-module JavaScript code + /// The expression is evaluated in the global context, so changes persist + /// + /// Awaits promise resolution, and runs the event loop to completion + /// + /// Asynchronous code is supported, partially + /// - Top-level await is not supported + /// - The event loop will be run to completion after the expression is evaluated + /// + /// For top-level await support, use one of: + /// - `call_function_async` + /// - `call_stored_function_async` + /// - `load_module_async` + /// - `load_modules_async` + /// + /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`] + /// + /// # Arguments + /// * `expr` - A string representing the JavaScript expression to evaluate + /// + /// # Returns + /// A `Result` containing the deserialized result of the expression (`T`) + /// or an error (`Error`) if the expression cannot be evaluated or if the + /// result cannot be deserialized. + /// + /// # Errors + /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type + /// + /// # Example + /// For an example, see [`Runtime::eval`] + pub async fn eval_async(&mut self, expr: impl ToString) -> Result + where + T: serde::de::DeserializeOwned, + { + let result = self.inner.eval(expr.to_string()).await?; + let result = self.inner.resolve_with_event_loop(result).await?; + self.inner.decode_value(result) + } + + /// Evaluate a piece of non-ECMAScript-module JavaScript code + /// The expression is evaluated in the global context, so changes persist + /// + /// Does not await promise resolution, or run the event loop + /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] + /// The event loop should be run using [`Runtime::await_event_loop`] + /// + /// Note that this function needs to be async because calls to `setTimeout` must be evaluated from within an async runtime. + /// + /// Asynchronous code is supported, partially + /// - Top-level await is not supported + /// + /// For top-level await support, use one of: + /// - `call_function_async` + /// - `call_stored_function_async` + /// - `load_module_async` + /// - `load_modules_async` + /// + /// Or any of the `_immmediate` variants, paired with [`crate::js_value::Promise`] + /// + /// # Arguments + /// * `expr` - A string representing the JavaScript expression to evaluate + /// + /// # Returns + /// A `Result` containing the deserialized result of the expression (`T`) + /// or an error (`Error`) if the expression cannot be evaluated or if the + /// result cannot be deserialized. + /// + /// # Errors + /// Can fail if the expression cannot be evaluated, or if the result cannot be deserialized into the requested type + /// + /// # Example + /// For an example, see [`Runtime::eval`] + pub async fn eval_immediate(&mut self, expr: impl ToString) -> Result + where + T: serde::de::DeserializeOwned, + { + let result = self.inner.eval(expr.to_string()).await?; + self.inner.decode_value(result) + } + + /// Calls a stored javascript function and deserializes its return value. + /// + /// Returns a future that resolves when: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// See [`Runtime::call_function`] for an example + /// + /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however. + /// + /// # Arguments + /// * `module_context` - Optional handle to a module providing global context for the function + /// * `function` - A The function object + /// * `args` - The arguments to pass to the function + /// + /// # Returns + /// A `Result` containing the deserialized result of the function call (`T`) + /// or an error (`Error`) if there are issues with calling the function, + /// or if the result cannot be deserialized. + /// + /// # Errors + /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type + pub async fn call_stored_function_async( + &mut self, + module_context: Option<&ModuleHandle>, + function: &Function, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: serde::de::DeserializeOwned, + { + let isolate = self.deno_runtime().v8_isolate(); + let function = function.as_global(isolate); + let result = self + .inner + .call_function_by_ref(module_context, &function, args)?; + let result = self.inner.resolve_with_event_loop(result).await?; + self.inner.decode_value(result) + } + + /// Calls a stored javascript function and deserializes its return value. + /// + /// Blocks until: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// See [`Runtime::call_function`] for an example + /// + /// # Arguments + /// * `module_context` - Optional handle to a module providing global context for the function + /// * `function` - A The function object + /// * `args` - The arguments to pass to the function + /// + /// # Returns + /// A `Result` containing the deserialized result of the function call (`T`) + /// or an error (`Error`) if there are issues with calling the function, + /// or if the result cannot be deserialized. + /// + /// # Errors + /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type + pub fn call_stored_function( + &mut self, + module_context: Option<&ModuleHandle>, + function: &Function, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + self.block_on(|runtime| async move { + runtime + .call_stored_function_async(module_context, function, args) + .await + }) + } + + /// Calls a stored javascript function and deserializes its return value. + /// + /// Will not attempt to resolve promises, or run the event loop + /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] + /// The event loop should be run using [`Runtime::await_event_loop`] + /// + /// See [`Runtime::call_function`] for an example + /// + /// # Arguments + /// * `module_context` - Optional handle to a module providing global context for the function + /// * `function` - A The function object + /// * `args` - The arguments to pass to the function + /// + /// # Returns + /// A `Result` containing the deserialized result of the function call (`T`) + /// or an error (`Error`) if there are issues with calling the function, + /// or if the result cannot be deserialized. + /// + /// # Errors + /// Can fail if there are issues with calling the function, or if the result cannot be deserialized into the requested type + pub fn call_stored_function_immediate( + &mut self, + module_context: Option<&ModuleHandle>, + function: &Function, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + let isolate = self.deno_runtime().v8_isolate(); + let function = function.as_global(isolate); + let result = self + .inner + .call_function_by_ref(module_context, &function, args)?; + self.inner.decode_value(result) + } + + /// Calls a javascript function within the Deno runtime by its name and deserializes its return value. + /// + /// Returns a future that resolves when: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however. + /// + /// See [`Runtime::call_function`] for an example + /// + /// # Arguments + /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used + /// * `name` - A string representing the name of the javascript function to call. + /// * `args` - The arguments to pass to the function + /// + /// # Returns + /// A `Result` containing the deserialized result of the function call (`T`) + /// or an error (`Error`) if the function cannot be found, if there are issues with + /// calling the function, or if the result cannot be deserialized. + /// + /// # Errors + /// Fails if the function cannot be found, if there are issues with calling the function, + /// Or if the result cannot be deserialized into the requested type + pub async fn call_function_async( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + let function = self.inner.get_function_by_name(module_context, name)?; + let result = self + .inner + .call_function_by_ref(module_context, &function, args)?; + let result = self.inner.resolve_with_event_loop(result).await?; + self.inner.decode_value(result) + } + + /// Calls a javascript function within the Deno runtime by its name and deserializes its return value. + /// + /// Blocks until: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// # Arguments + /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used + /// * `name` - A string representing the name of the javascript function to call. + /// * `args` - The arguments to pass to the function + /// + /// # Returns + /// A `Result` containing the deserialized result of the function call (`T`) + /// or an error (`Error`) if the function cannot be found, if there are issues with + /// calling the function, or if the result cannot be deserialized. + /// + /// # Errors + /// Fails if the function cannot be found, if there are issues with calling the function, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Example + /// + /// ```no_run + /// use rustyscript::{ json_args, Runtime, Module, Error }; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("/path/to/module.js", "export function f() { return 2; };"); + /// let module = runtime.load_module(&module)?; + /// let value: usize = runtime.call_function(Some(&module), "f", json_args!())?; + /// # Ok(()) + /// # } + /// ``` + pub fn call_function( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + self.block_on(|runtime| async move { + runtime + .call_function_async(module_context, name, args) + .await + }) + } + + /// Calls a javascript function within the Deno runtime by its name and deserializes its return value. + /// + /// Will not attempt to resolve promises, or run the event loop + /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] + /// The event loop should be run using [`Runtime::await_event_loop`] + /// + /// # Arguments + /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used + /// * `name` - A string representing the name of the javascript function to call. + /// * `args` - The arguments to pass to the function + /// + /// # Returns + /// A `Result` containing the deserialized result of the function call (`T`) + /// or an error (`Error`) if the function cannot be found, if there are issues with + /// calling the function, or if the result cannot be deserialized. + /// + /// # Errors + /// Fails if the function cannot be found, if there are issues with calling the function, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Example + /// + /// ```no_run + /// use rustyscript::{ json_args, Runtime, Module, Error }; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("/path/to/module.js", "export function f() { return 2; };"); + /// let module = runtime.load_module(&module)?; + /// let value: usize = runtime.call_function_immediate(Some(&module), "f", json_args!())?; + /// # Ok(()) + /// # } + /// ``` + pub fn call_function_immediate( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + let function = self.inner.get_function_by_name(module_context, name)?; + let result = self + .inner + .call_function_by_ref(module_context, &function, args)?; + self.inner.decode_value(result) + } + + /// Get a value from a runtime instance + /// + /// Blocks until: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// # Arguments + /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used + /// * `name` - A string representing the name of the value to find + /// + /// # Returns + /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Errors + /// Can fail if the value cannot be found, or if the result cannot be deserialized. + /// + /// # Example + /// + /// ```no_run + /// use rustyscript::{ Runtime, Module, Error }; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("/path/to/module.js", "globalThis.my_value = 2;"); + /// let module = runtime.load_module(&module)?; + /// let value: usize = runtime.get_value(Some(&module), "my_value")?; + /// # Ok(()) + /// # } + /// ``` + pub fn get_value( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + ) -> Result + where + T: serde::de::DeserializeOwned, + { + self.block_on(|runtime| async move { runtime.get_value_async(module_context, name).await }) + } + + /// Get a value from a runtime instance + /// + /// Returns a future that resolves when: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// See [`Runtime::get_value`] for an example + /// + /// # Arguments + /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used + /// * `name` - A string representing the name of the value to find + /// + /// # Returns + /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Errors + /// Can fail if the value cannot be found, or if the result cannot be deserialized. + pub async fn get_value_async( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + ) -> Result + where + T: serde::de::DeserializeOwned, + { + let result = self.inner.get_value_ref(module_context, name)?; + let result = self.inner.resolve_with_event_loop(result).await?; + self.inner.decode_value(result) + } + + /// Get a value from a runtime instance + /// + /// Will not attempt to resolve promises, or run the event loop + /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] + /// The event loop should be run using [`Runtime::await_event_loop`] + /// + /// # Arguments + /// * `module_context` - Optional handle to a module to search - if None, or if the search fails, the global context is used + /// * `name` - A string representing the name of the value to find + /// + /// # Returns + /// A `Result` containing the deserialized result or an error (`Error`) if the value cannot be found, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Errors + /// Can fail if the value cannot be found, or if the result cannot be deserialized. + /// + /// # Example + /// + /// ```no_run + /// use rustyscript::{ Runtime, Module, Error }; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("/path/to/module.js", "globalThis.my_value = 2;"); + /// let module = runtime.load_module(&module)?; + /// let value: usize = runtime.get_value_immediate(Some(&module), "my_value")?; + /// # Ok(()) + /// # } + /// ``` + pub fn get_value_immediate( + &mut self, + module_context: Option<&ModuleHandle>, + name: &str, + ) -> Result + where + T: serde::de::DeserializeOwned, + { + let result = self.inner.get_value_ref(module_context, name)?; + self.inner.decode_value(result) + } + + /// Executes the given module, and returns a handle allowing you to extract values + /// and call functions + /// + /// Blocks until the module has been executed AND the event loop has fully resolved + /// See [`Runtime::load_module_async`] for a non-blocking variant, or use with async + /// background tasks + /// + /// # Arguments + /// * `module` - A `Module` object containing the module's filename and contents. + /// + /// # Returns + /// A `Result` containing a handle for the loaded module + /// or an error (`Error`) if there are issues with loading or executing the module + /// + /// # Errors + /// Can fail if the module cannot be loaded, or execution fails + /// + /// # Example + /// + /// ```no_run + /// // Create a module with filename and contents + /// use rustyscript::{Runtime, Module, Error}; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("test.js", "export default () => 'test'"); + /// runtime.load_module(&module); + /// # Ok(()) + /// # } + /// ``` + pub fn load_module(&mut self, module: &Module) -> Result { + self.block_on(|runtime| async move { + let handle = runtime.load_module_async(module).await; + runtime + .await_event_loop(PollEventLoopOptions::default(), None) + .await?; + handle + }) + } + + /// Executes the given module, and returns a handle allowing you to extract values + /// and call functions + /// + /// Returns a future that resolves to the handle for the loaded module + /// Makes no attempt to fully resolve the event loop - call [`Runtime::await_event_loop`] + /// to resolve background tasks and async listeners + /// + /// # Arguments + /// * `module` - A `Module` object containing the module's filename and contents. + /// + /// # Returns + /// A `Result` containing a handle for the loaded module + /// or an error (`Error`) if there are issues with loading or executing the module + /// + /// # Errors + /// Can fail if the module cannot be loaded, or execution fails + /// + /// See [`Runtime::load_module`] for an example + pub async fn load_module_async(&mut self, module: &Module) -> Result { + self.inner.load_modules(None, vec![module]).await + } + + /// Executes the given module, and returns a handle allowing you to extract values + /// and call functions. + /// + /// Blocks until all modules have been executed AND the event loop has fully resolved + /// See [`Runtime::load_module_async`] for a non-blocking variant, or use with async + /// background tasks + /// + /// This will load 'module' as the main module, and the others as side-modules. + /// Only one main module can be loaded per runtime + /// + /// # Arguments + /// * `module` - A `Module` object containing the module's filename and contents. + /// * `side_modules` - A set of additional modules to be loaded into memory for use + /// + /// # Returns + /// A `Result` containing a handle for the loaded module + /// or an error (`Error`) if there are issues with loading or executing the module + /// + /// # Errors + /// Can fail if the module cannot be loaded, or execution fails + /// + /// # Example + /// + /// ```no_run + /// // Create a module with filename and contents + /// use rustyscript::{Runtime, Module, Error}; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("test.js", "export default () => 'test'"); + /// runtime.load_modules(&module, vec![]); + /// # Ok(()) + /// # } + /// ``` + pub fn load_modules( + &mut self, + module: &Module, + side_modules: Vec<&Module>, + ) -> Result { + self.block_on(move |runtime| async move { + let handle = runtime.load_modules_async(module, side_modules).await; + runtime + .await_event_loop(PollEventLoopOptions::default(), None) + .await?; + handle + }) + } + + /// Executes the given module, and returns a handle allowing you to extract values + /// and call functions. + /// + /// Returns a future that resolves to the handle for the loaded module + /// Makes no attempt to resolve the event loop - call [`Runtime::await_event_loop`] to + /// resolve background tasks and async listeners + /// + /// This will load 'module' as the main module, and the others as side-modules. + /// Only one main module can be loaded per runtime + /// + /// See [`Runtime::load_modules`] for an example + /// + /// # Arguments + /// * `module` - A `Module` object containing the module's filename and contents. + /// * `side_modules` - A set of additional modules to be loaded into memory for use + /// + /// # Returns + /// A `Result` containing a handle for the loaded main module, or the last side-module + /// or an error (`Error`) if there are issues with loading or executing the modules + /// + /// # Errors + /// Can fail if the modules cannot be loaded, or execution fails + pub async fn load_modules_async( + &mut self, + module: &Module, + side_modules: Vec<&Module>, + ) -> Result { + self.inner.load_modules(Some(module), side_modules).await + } + + /// Executes the entrypoint function of a module within the Deno runtime. + /// + /// Blocks until: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// # Arguments + /// * `module_context` - A handle returned by loading a module into the runtime + /// + /// # Returns + /// A `Result` containing the deserialized result of the entrypoint execution (`T`) + /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, + /// or the result cannot be deserialized. + /// + /// # Errors + /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Example + /// + /// ```no_run + /// use rustyscript::{json_args, Runtime, Module, Error}; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("test.js", "export default () => 'test'"); + /// let module = runtime.load_module(&module)?; + /// + /// // Run the entrypoint and handle the result + /// let value: String = runtime.call_entrypoint(&module, json_args!())?; + /// # Ok(()) + /// # } + /// ``` + pub fn call_entrypoint( + &mut self, + module_context: &ModuleHandle, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + self.block_on( + |runtime| async move { runtime.call_entrypoint_async(module_context, args).await }, + ) + } + + /// Executes the entrypoint function of a module within the Deno runtime. + /// + /// Returns a future that resolves when: + /// - The event loop is resolved, and + /// - If the value is a promise, the promise is resolved + /// + /// Note that synchronous functions are run synchronously. Returned promises will be run asynchronously, however. + /// + /// See [`Runtime::call_entrypoint`] for an example + /// + /// # Arguments + /// * `module_context` - A handle returned by loading a module into the runtime + /// + /// # Returns + /// A `Result` containing the deserialized result of the entrypoint execution (`T`) + /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, + /// or the result cannot be deserialized. + /// + /// # Errors + /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, + /// Or if the result cannot be deserialized into the requested type + pub async fn call_entrypoint_async( + &mut self, + module_context: &ModuleHandle, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + if let Some(entrypoint) = module_context.entrypoint() { + let result = self + .inner + .call_function_by_ref(Some(module_context), entrypoint, args)?; + let result = self.inner.resolve_with_event_loop(result).await?; + self.inner.decode_value(result) + } else { + Err(Error::MissingEntrypoint(module_context.module().clone())) + } + } + + /// Executes the entrypoint function of a module within the Deno runtime. + /// + /// Will not attempt to resolve promises, or run the event loop + /// Promises can be returned by specifying the return type as [`crate::js_value::Promise`] + /// The event loop should be run using [`Runtime::await_event_loop`] + /// + /// # Arguments + /// * `module_context` - A handle returned by loading a module into the runtime + /// + /// # Returns + /// A `Result` containing the deserialized result of the entrypoint execution (`T`) + /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, + /// or the result cannot be deserialized. + /// + /// # Errors + /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Example + /// + /// ```no_run + /// use rustyscript::{json_args, Runtime, Module, Error}; + /// + /// # fn main() -> Result<(), Error> { + /// let mut runtime = Runtime::new(Default::default())?; + /// let module = Module::new("test.js", "export default () => 'test'"); + /// let module = runtime.load_module(&module)?; + /// + /// // Run the entrypoint and handle the result + /// let value: String = runtime.call_entrypoint_immediate(&module, json_args!())?; + /// # Ok(()) + /// # } + /// ``` + pub fn call_entrypoint_immediate( + &mut self, + module_context: &ModuleHandle, + args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + if let Some(entrypoint) = module_context.entrypoint() { + let result = self.block_on(|runtime| async move { + runtime + .inner + .call_function_by_ref(Some(module_context), entrypoint, args) + })?; + self.inner.decode_value(result) + } else { + Err(Error::MissingEntrypoint(module_context.module().clone())) + } + } + + /// Loads a module into a new runtime, executes the entry function and returns the + /// result of the module's execution, deserialized into the specified Rust type (`T`). + /// + /// # Arguments + /// * `module` - A `Module` object containing the module's filename and contents. + /// * `side_modules` - A set of additional modules to be loaded into memory for use + /// * `runtime_options` - Options for the creation of the runtime + /// * `entrypoint_args` - Arguments to pass to the entrypoint function + /// + /// # Returns + /// A `Result` containing the deserialized result of the entrypoint execution (`T`) + /// if successful, or an error (`Error`) if the entrypoint is missing, the execution fails, + /// or the result cannot be deserialized. + /// + /// # Errors + /// Can fail if the module cannot be loaded, if the entrypoint is missing, if the execution fails, + /// Or if the result cannot be deserialized into the requested type + /// + /// # Example + /// + /// ```no_run + /// // Create a module with filename and contents + /// use rustyscript::{json_args, Runtime, Module, Error}; + /// + /// # fn main() -> Result<(), Error> { + /// let module = Module::new("test.js", "export default () => 2"); + /// let value: usize = Runtime::execute_module(&module, vec![], Default::default(), json_args!())?; + /// # Ok(()) + /// # } + /// ``` + pub fn execute_module( + module: &Module, + side_modules: Vec<&Module>, + runtime_options: RuntimeOptions, + entrypoint_args: &impl serde::ser::Serialize, + ) -> Result + where + T: deno_core::serde::de::DeserializeOwned, + { + let mut runtime = Runtime::new(runtime_options)?; + let module = runtime.load_modules(module, side_modules)?; + let value: T = runtime.call_entrypoint(&module, entrypoint_args)?; + Ok(value) + } +} + +impl AsyncBridgeExt for Runtime { + fn bridge(&self) -> &AsyncBridge { + &self.tokio + } +} + +#[cfg(test)] +mod test_runtime { + use crate::json_args; + use std::time::Duration; + + use super::*; + use deno_core::extension; + + #[test] + fn test_new() { + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + + extension!(test_extension); + Runtime::new(RuntimeOptions { + extensions: vec![test_extension::init()], + ..Default::default() + }) + .expect("Could not create runtime with extensions"); + } + + #[test] + fn test_get_value() { + let module = Module::new( + "test.js", + " + globalThis.a = 2; + export const b = 'test'; + export const fnc = null; + ", + ); + + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module = runtime + .load_modules(&module, vec![]) + .expect("Could not load module"); + + assert_eq!( + 2, + runtime + .get_value::(Some(&module), "a") + .expect("Could not find global") + ); + assert_eq!( + "test", + runtime + .get_value::(Some(&module), "b") + .expect("Could not find export") + ); + runtime + .get_value::(Some(&module), "c") + .expect_err("Could not detect null"); + runtime + .get_value::(Some(&module), "d") + .expect_err("Could not detect undeclared"); + } + + #[test] + fn test_load_module() { + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module = Module::new( + "test.js", + " + export default () => 2; + ", + ); + let module = runtime + .load_modules(&module, vec![]) + .expect("Could not load module"); + assert_ne!(0, module.id()); + + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module1 = Module::new( + "importme.js", + " + export const value = 2; + ", + ); + let module2 = Module::new( + "test.js", + " + import { value } from './importme.js'; + rustyscript.register_entrypoint(() => value); + ", + ); + runtime + .load_module(&module1) + .expect("Could not load modules"); + let module = runtime + .load_module(&module2) + .expect("Could not load modules"); + let value: usize = runtime + .call_entrypoint(&module, json_args!()) + .expect("Could not call exported fn"); + assert_eq!(2, value); + + let mut runtime = Runtime::new(RuntimeOptions { + timeout: Duration::from_millis(50), + ..Default::default() + }) + .expect("Could not create the runtime"); + let module = Module::new( + "test.js", + " + await new Promise(r => setTimeout(r, 2000)); + ", + ); + runtime + .load_modules(&module, vec![]) + .expect_err("Did not interupt after timeout"); + } + + #[test] + fn test_load_modules() { + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module = Module::new( + "test.js", + " + rustyscript.register_entrypoint(() => 2); + ", + ); + let module = runtime + .load_modules(&module, vec![]) + .expect("Could not load module"); + assert_ne!(0, module.id()); + + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module1 = Module::new( + "importme.js", + " + export const value = 2; + ", + ); + let module2 = Module::new( + "test.js", + " + import { value } from './importme.js'; + rustyscript.register_entrypoint(() => value); + ", + ); + let module = runtime + .load_modules(&module2, vec![&module1]) + .expect("Could not load modules"); + let value: usize = runtime + .call_entrypoint(&module, json_args!()) + .expect("Could not call exported fn"); + assert_eq!(2, value); + + let mut runtime = Runtime::new(RuntimeOptions { + timeout: Duration::from_millis(50), + ..Default::default() + }) + .expect("Could not create the runtime"); + let module = Module::new( + "test.js", + " + await new Promise(r => setTimeout(r, 5000)); + ", + ); + runtime + .load_modules(&module, vec![]) + .expect_err("Did not interupt after timeout"); + } + + #[test] + fn test_call_entrypoint() { + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module = Module::new( + "test.js", + " + rustyscript.register_entrypoint(() => 2); + ", + ); + let module = runtime + .load_modules(&module, vec![]) + .expect("Could not load module"); + let value: usize = runtime + .call_entrypoint(&module, json_args!()) + .expect("Could not call registered fn"); + assert_eq!(2, value); + + let mut runtime = Runtime::new(RuntimeOptions { + default_entrypoint: Some("load".to_string()), + ..Default::default() + }) + .expect("Could not create the runtime"); + let module = Module::new( + "test.js", + " + export const load = () => 2; + ", + ); + let module = runtime + .load_modules(&module, vec![]) + .expect("Could not load module"); + let value: usize = runtime + .call_entrypoint(&module, json_args!()) + .expect("Could not call exported fn"); + assert_eq!(2, value); + + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module = Module::new( + "test.js", + " + export const load = () => 2; + ", + ); + let module = runtime + .load_modules(&module, vec![]) + .expect("Could not load module"); + runtime + .call_entrypoint::(&module, json_args!()) + .expect_err("Did not detect no entrypoint"); + } + + #[test] + fn test_execute_module() { + let module = Module::new( + "test.js", + " + rustyscript.register_entrypoint(() => 2); + ", + ); + let value: usize = + Runtime::execute_module(&module, vec![], RuntimeOptions::default(), json_args!()) + .expect("Could not exec module"); + assert_eq!(2, value); + + let module = Module::new( + "test.js", + " + function load() { return 2; } + ", + ); + Runtime::execute_module::( + &module, + vec![], + RuntimeOptions::default(), + json_args!(), + ) + .expect_err("Could not detect no entrypoint"); + } + + #[test] + fn call_function() { + let module = Module::new( + "test.js", + " + globalThis.fna = (i) => i; + export function fnb() { return 'test'; } + export const fnc = 2; + export const fne = () => {}; + ", + ); + + let mut runtime = + Runtime::new(RuntimeOptions::default()).expect("Could not create the runtime"); + let module = runtime + .load_modules(&module, vec![]) + .expect("Could not load module"); + + let result: usize = runtime + .call_function(Some(&module), "fna", json_args!(2)) + .expect("Could not call global"); + assert_eq!(2, result); + + let result: String = runtime + .call_function(Some(&module), "fnb", json_args!()) + .expect("Could not call export"); + assert_eq!("test", result); + + runtime + .call_function::(Some(&module), "fnc", json_args!()) + .expect_err("Did not detect non-function"); + runtime + .call_function::(Some(&module), "fnd", json_args!()) + .expect_err("Did not detect undefined"); + runtime + .call_function::(Some(&module), "fne", json_args!()) + .expect("Did not allow undefined return"); + } + + #[test] + fn test_heap_exhaustion_handled() { + let mut runtime = Runtime::new(RuntimeOptions { + max_heap_size: Some(100 * 1024 * 1024), + ..Default::default() + }) + .expect("Could not create the runtime"); + let module = Module::new( + "test.js", + "const largeArray = new Array(40 * 1024 * 1024).fill('a');", + ); + runtime + .load_modules(&module, vec![]) + .expect_err("Did not detect heap exhaustion"); + } +} diff --git a/src/runtime_builder.rs b/src/runtime_builder.rs index 84abd5a..198fef5 100644 --- a/src/runtime_builder.rs +++ b/src/runtime_builder.rs @@ -1,361 +1,358 @@ -use crate::{module_loader::ImportProvider, Error, RuntimeOptions}; - -/// A builder for creating a new runtime -/// -/// Just a helper wrapper around `RuntimeOptions` for `Runtime` and `SnapshotBuilder` -/// -/// # Example -/// ```rust -/// use rustyscript::RuntimeBuilder; -/// -/// let runtime = RuntimeBuilder::new() -/// .with_timeout(std::time::Duration::from_secs(5)) -/// .with_default_entrypoint("main".to_string()) -/// .with_cryto_seed(42) -/// .build() -/// .expect("Failed to create runtime"); -/// ``` -pub struct RuntimeBuilder(RuntimeOptions); -impl RuntimeBuilder { - /// Create a new runtime builder with default options - #[must_use] - pub fn new() -> Self { - Self(RuntimeOptions::default()) - } - - /// Add an extension to the runtime - /// - /// This can be used to add custom functionality to the runtime - /// - #[must_use] - pub fn with_extension(mut self, extension: deno_core::Extension) -> Self { - self.0.extensions.push(extension); - self - } - - /// Add multiple extensions to the runtime - /// - /// This can be used to add custom functionality to the runtime - /// - #[must_use] - pub fn with_extensions(mut self, extensions: Vec) -> Self { - self.0.extensions.extend(extensions); - self - } - - /// Set the default entrypoint for the runtime - /// - /// This is the function to use as entrypoint if a module does not provide one - #[must_use] - pub fn with_default_entrypoint(mut self, entrypoint: String) -> Self { - self.0.default_entrypoint = Some(entrypoint); - self - } - - /// Set the timeout for the runtime - /// - /// This is the maximum time a script can run before it is terminated - #[must_use] - pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self { - self.0.timeout = timeout; - self - } - - /// Optional maximum heap size for the runtime - #[must_use] - pub fn with_max_heap_size(mut self, max_heap_size: usize) -> Self { - self.0.max_heap_size = Some(max_heap_size); - self - } - - /// Optional import provider for the module loader - #[must_use] - pub fn with_import_provider(mut self, import_provider: Box) -> Self { - self.0.import_provider = Some(import_provider); - self - } - - /// Set the startup snapshot for the runtime - /// - /// This will reduce load times, but requires the same extensions to be loaded as when the snapshot was created - /// - /// WARNING: Snapshots MUST be used on the same system they were created on - #[must_use] - pub fn with_startup_snapshot(mut self, snapshot: &'static [u8]) -> Self { - self.0.startup_snapshot = Some(snapshot); - self - } - - /// Set the params used to create the underlying V8 isolate - /// - /// This can be used to alter the behavior of the runtime. - /// - /// See the `rusty_v8` documentation for more information - #[must_use] - pub fn with_isolate_params(mut self, params: deno_core::v8::CreateParams) -> Self { - self.0.isolate_params = Some(params); - self - } - - /// Set the shared array buffer store to use for the runtime - /// - /// Allows data-sharing between runtimes across threads - #[must_use] - pub fn with_shared_array_buffer_store( - mut self, - store: deno_core::SharedArrayBufferStore, - ) -> Self { - self.0.shared_array_buffer_store = Some(store); - self - } - - /// Add to a whitelist of custom schema prefixes that are allowed to be loaded from javascript - /// - /// By default only http/https (`url_import` crate feature), and file (`fs_import` crate feature) are allowed - #[must_use] - pub fn with_schema(mut self, schema: impl ToString) -> Self { - self.0.schema_whlist.insert(schema.to_string()); - self - } - - // - // Extension options - // - - /// Set the initial seed for the crypto extension - #[cfg(feature = "crypto")] - #[cfg_attr(docsrs, doc(cfg(feature = "crypto")))] - #[must_use] - pub fn with_cryto_seed(mut self, seed: u64) -> Self { - self.0.extension_options.crypto_seed = Some(seed); - self - } - - /// Set the options for the io extension - #[cfg(feature = "io")] - #[cfg_attr(docsrs, doc(cfg(feature = "io")))] - #[must_use] - pub fn with_io_pipes(mut self, pipes: deno_io::Stdio) -> Self { - self.0.extension_options.io_pipes = Some(pipes); - self - } - - /// Set the options for the webstorage extension - #[cfg(feature = "webstorage")] - #[cfg_attr(docsrs, doc(cfg(feature = "webstorage")))] - #[must_use] - pub fn with_webstorage_origin_storage_dir(mut self, dir: std::path::PathBuf) -> Self { - self.0.extension_options.webstorage_origin_storage_dir = Some(dir); - self - } - - /// Set the options for the cache extension - #[cfg(feature = "cache")] - #[cfg_attr(docsrs, doc(cfg(feature = "cache")))] - #[must_use] - pub fn with_cache(mut self, cache: deno_cache::CreateCache) -> Self { - self.0.extension_options.cache = Some(cache); - self - } - - /// Set the options for the broadcast channel extension - #[cfg(feature = "broadcast_channel")] - #[cfg_attr(docsrs, doc(cfg(feature = "broadcast_channel")))] - #[must_use] - pub fn with_broadcast_channel( - mut self, - channel: deno_broadcast_channel::InMemoryBroadcastChannel, - ) -> Self { - self.0.extension_options.broadcast_channel = channel; - self - } - - /// Set the options for the kv store extension - #[cfg(feature = "kv")] - #[cfg_attr(docsrs, doc(cfg(feature = "kv")))] - #[must_use] - pub fn with_kv_store(mut self, kv_store: crate::KvStore) -> Self { - self.0.extension_options.kv_store = kv_store; - self - } - - /// Set the options for the node extension - #[cfg(feature = "node_experimental")] - #[cfg_attr(docsrs, doc(cfg(feature = "node_experimental")))] - #[must_use] - pub fn with_node_resolver(mut self, resolver: std::sync::Arc) -> Self { - self.0.extension_options.node_resolver = resolver; - self - } - - // - // Web options - // - - /// Base URL for some `deno_web` OPs - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_base_url(mut self, base_url: deno_core::ModuleSpecifier) -> Self { - self.0.extension_options.web.base_url = Some(base_url); - self - } - - /// User agent to use for fetch - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_user_agent(mut self, user_agent: String) -> Self { - self.0.extension_options.web.user_agent = user_agent; - self - } - - /// Root certificate store for TLS connections for fetches and network OPs - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_root_cert_store_provider( - mut self, - root_cert_store_provider: std::sync::Arc, - ) -> Self { - self.0.extension_options.web.root_cert_store_provider = Some(root_cert_store_provider); - self - } - - /// Proxy for fetch - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_proxy(mut self, proxy: deno_tls::Proxy) -> Self { - self.0.extension_options.web.proxy = Some(proxy); - self - } - - /// Request builder hook for fetch - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_request_builder_hook( - mut self, - hook: fn(&mut http::Request) -> Result<(), deno_error::JsErrorBox>, - ) -> Self { - self.0.extension_options.web.request_builder_hook = Some(hook); - self - } - - /// List of domain names or IP addresses for which fetches and network OPs will ignore SSL errors - /// - /// This is useful for testing with self-signed certificates - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_unsafely_ignored_certificate_errors(mut self, domain: impl ToString) -> Self { - match &mut self - .0 - .extension_options - .web - .unsafely_ignore_certificate_errors - { - Some(vec) => vec.push(domain.to_string()), - None => { - self.0 - .extension_options - .web - .unsafely_ignore_certificate_errors = Some(vec![domain.to_string()]); - } - } - - self - } - - /// Client certificate and key for fetch - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_client_cert_chain_and_key(mut self, keys: deno_tls::TlsKeys) -> Self { - self.0.extension_options.web.client_cert_chain_and_key = keys; - self - } - - /// File fetch handler for fetch - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_file_fetch_handler( - mut self, - handler: std::rc::Rc, - ) -> Self { - self.0.extension_options.web.file_fetch_handler = handler; - self - } - - /// Permissions manager for sandbox-breaking extensions - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_permissions( - mut self, - permissions: std::sync::Arc, - ) -> Self { - self.0.extension_options.web.permissions = permissions; - self - } - - /// Blob store for the web related extensions - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_blob_store(mut self, blob_store: std::sync::Arc) -> Self { - self.0.extension_options.web.blob_store = blob_store; - self - } - - /// A callback to customize HTTP client configuration. - /// - /// For more info on what can be configured, see [`hyper_util::client::legacy::Builder`] - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_client_builder_hook( - mut self, - hook: Option< - fn(hyper_util::client::legacy::Builder) -> hyper_util::client::legacy::Builder, - >, - ) -> Self { - self.0.extension_options.web.client_builder_hook = hook; - self - } - - /// Resolver for DNS resolution - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - #[must_use] - pub fn with_web_resolver(mut self, resolver: deno_fetch::dns::Resolver) -> Self { - self.0.extension_options.web.resolver = resolver; - self - } - - /// Consume the builder and create a new runtime with the given options - /// - /// # Errors - /// Will return an error if the runtime cannot be created (usually an issue with extensions) - pub fn build(self) -> Result { - crate::Runtime::new(self.0) - } - - /// Consume the builder and create a new snapshot runtime with the given options - /// - /// # Errors - /// Will return an error if the runtime cannot be created (usually an issue with extensions) - #[cfg(feature = "snapshot_builder")] - #[cfg_attr(docsrs, doc(cfg(feature = "snapshot_builder")))] - pub fn build_snapshot(self) -> Result { - crate::SnapshotBuilder::new(self.0) - } -} - -impl Default for RuntimeBuilder { - fn default() -> Self { - Self::new() - } -} +use crate::{module_loader::ImportProvider, Error, RuntimeOptions}; + +/// A builder for creating a new runtime +/// +/// Just a helper wrapper around `RuntimeOptions` for `Runtime` and `SnapshotBuilder` +/// +/// # Example +/// ```no_run +/// use rustyscript::RuntimeBuilder; +/// +/// let runtime = RuntimeBuilder::new() +/// .with_timeout(std::time::Duration::from_secs(5)) +/// .with_default_entrypoint("main".to_string()) +/// .with_cryto_seed(42) +/// .build() +/// .expect("Failed to create runtime"); +/// ``` +pub struct RuntimeBuilder(RuntimeOptions); +impl RuntimeBuilder { + /// Create a new runtime builder with default options + #[must_use] + pub fn new() -> Self { + Self(RuntimeOptions::default()) + } + + /// Add an extension to the runtime + /// + /// This can be used to add custom functionality to the runtime + /// + #[must_use] + pub fn with_extension(mut self, extension: deno_core::Extension) -> Self { + self.0.extensions.push(extension); + self + } + + /// Add multiple extensions to the runtime + /// + /// This can be used to add custom functionality to the runtime + /// + #[must_use] + pub fn with_extensions(mut self, extensions: Vec) -> Self { + self.0.extensions.extend(extensions); + self + } + + /// Set the default entrypoint for the runtime + /// + /// This is the function to use as entrypoint if a module does not provide one + #[must_use] + pub fn with_default_entrypoint(mut self, entrypoint: String) -> Self { + self.0.default_entrypoint = Some(entrypoint); + self + } + + /// Set the timeout for the runtime + /// + /// This is the maximum time a script can run before it is terminated + #[must_use] + pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self { + self.0.timeout = timeout; + self + } + + /// Optional maximum heap size for the runtime + #[must_use] + pub fn with_max_heap_size(mut self, max_heap_size: usize) -> Self { + self.0.max_heap_size = Some(max_heap_size); + self + } + + /// Optional import provider for the module loader + #[must_use] + pub fn with_import_provider(mut self, import_provider: Box) -> Self { + self.0.import_provider = Some(import_provider); + self + } + + /// Set the startup snapshot for the runtime + /// + /// This will reduce load times, but requires the same extensions to be loaded as when the snapshot was created + /// + /// WARNING: Snapshots MUST be used on the same system they were created on + #[must_use] + pub fn with_startup_snapshot(mut self, snapshot: &'static [u8]) -> Self { + self.0.startup_snapshot = Some(snapshot); + self + } + + /// Set the params used to create the underlying V8 isolate + /// + /// This can be used to alter the behavior of the runtime. + /// + /// See the `rusty_v8` documentation for more information + #[must_use] + pub fn with_isolate_params(mut self, params: deno_core::v8::CreateParams) -> Self { + self.0.isolate_params = Some(params); + self + } + + /// Set the shared array buffer store to use for the runtime + /// + /// Allows data-sharing between runtimes across threads + #[must_use] + pub fn with_shared_array_buffer_store( + mut self, + store: deno_core::SharedArrayBufferStore, + ) -> Self { + self.0.shared_array_buffer_store = Some(store); + self + } + + /// Add to a whitelist of custom schema prefixes that are allowed to be loaded from javascript + /// + /// By default only http/https (`url_import` crate feature), and file (`fs_import` crate feature) are allowed + #[must_use] + pub fn with_schema(mut self, schema: impl ToString) -> Self { + self.0.schema_whlist.insert(schema.to_string()); + self + } + + // + // Extension options + // + + /// Set the initial seed for the crypto extension + #[cfg(feature = "crypto")] + #[cfg_attr(docsrs, doc(cfg(feature = "crypto")))] + #[must_use] + pub fn with_cryto_seed(mut self, seed: u64) -> Self { + self.0.extension_options.crypto_seed = Some(seed); + self + } + + /// Set the options for the io extension + #[cfg(feature = "io")] + #[cfg_attr(docsrs, doc(cfg(feature = "io")))] + #[must_use] + pub fn with_io_pipes(mut self, pipes: deno_io::Stdio) -> Self { + self.0.extension_options.io_pipes = Some(pipes); + self + } + + /// Set the options for the webstorage extension + #[cfg(feature = "webstorage")] + #[cfg_attr(docsrs, doc(cfg(feature = "webstorage")))] + #[must_use] + pub fn with_webstorage_origin_storage_dir(mut self, dir: std::path::PathBuf) -> Self { + self.0.extension_options.webstorage_origin_storage_dir = Some(dir); + self + } + + /// Set the options for the cache extension + #[cfg(feature = "cache")] + #[cfg_attr(docsrs, doc(cfg(feature = "cache")))] + #[must_use] + pub fn with_cache(mut self, cache: deno_cache::CreateCache) -> Self { + self.0.extension_options.cache = Some(cache); + self + } + + /// Set the options for the broadcast channel extension + #[cfg(feature = "broadcast_channel")] + #[cfg_attr(docsrs, doc(cfg(feature = "broadcast_channel")))] + #[must_use] + pub fn with_broadcast_channel(mut self, channel: deno_web::InMemoryBroadcastChannel) -> Self { + self.0.extension_options.broadcast_channel = channel; + self + } + + /// Set the options for the kv store extension + #[cfg(feature = "kv")] + #[cfg_attr(docsrs, doc(cfg(feature = "kv")))] + #[must_use] + pub fn with_kv_store(mut self, kv_store: crate::KvStore) -> Self { + self.0.extension_options.kv_store = kv_store; + self + } + + /// Set the options for the node extension + #[cfg(feature = "node_experimental")] + #[cfg_attr(docsrs, doc(cfg(feature = "node_experimental")))] + #[must_use] + pub fn with_node_resolver(mut self, resolver: std::sync::Arc) -> Self { + self.0.extension_options.node_resolver = resolver; + self + } + + // + // Web options + // + + /// Base URL for some `deno_web` OPs + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_base_url(mut self, base_url: deno_core::ModuleSpecifier) -> Self { + self.0.extension_options.web.base_url = Some(base_url); + self + } + + /// User agent to use for fetch + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_user_agent(mut self, user_agent: String) -> Self { + self.0.extension_options.web.user_agent = user_agent; + self + } + + /// Root certificate store for TLS connections for fetches and network OPs + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_root_cert_store_provider( + mut self, + root_cert_store_provider: std::sync::Arc, + ) -> Self { + self.0.extension_options.web.root_cert_store_provider = Some(root_cert_store_provider); + self + } + + /// Proxy for fetch + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_proxy(mut self, proxy: deno_tls::Proxy) -> Self { + self.0.extension_options.web.proxy = Some(proxy); + self + } + + /// Request builder hook for fetch + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_request_builder_hook( + mut self, + hook: fn(&mut http::Request) -> Result<(), deno_error::JsErrorBox>, + ) -> Self { + self.0.extension_options.web.request_builder_hook = Some(hook); + self + } + + /// List of domain names or IP addresses for which fetches and network OPs will ignore SSL errors + /// + /// This is useful for testing with self-signed certificates + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_unsafely_ignored_certificate_errors(mut self, domain: impl ToString) -> Self { + match &mut self + .0 + .extension_options + .web + .unsafely_ignore_certificate_errors + { + Some(vec) => vec.push(domain.to_string()), + None => { + self.0 + .extension_options + .web + .unsafely_ignore_certificate_errors = Some(vec![domain.to_string()]); + } + } + + self + } + + /// Client certificate and key for fetch + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_client_cert_chain_and_key(mut self, keys: deno_tls::TlsKeys) -> Self { + self.0.extension_options.web.client_cert_chain_and_key = keys; + self + } + + /// File fetch handler for fetch + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_file_fetch_handler( + mut self, + handler: std::rc::Rc, + ) -> Self { + self.0.extension_options.web.file_fetch_handler = handler; + self + } + + /// Permissions manager for sandbox-breaking extensions + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_permissions( + mut self, + permissions: std::sync::Arc, + ) -> Self { + self.0.extension_options.web.permissions = permissions; + self + } + + /// Blob store for the web related extensions + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_blob_store(mut self, blob_store: std::sync::Arc) -> Self { + self.0.extension_options.web.blob_store = blob_store; + self + } + + /// A callback to customize HTTP client configuration. + /// + /// For more info on what can be configured, see [`hyper_util::client::legacy::Builder`] + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_client_builder_hook( + mut self, + hook: Option< + fn(hyper_util::client::legacy::Builder) -> hyper_util::client::legacy::Builder, + >, + ) -> Self { + self.0.extension_options.web.client_builder_hook = hook; + self + } + + /// Resolver for DNS resolution + #[cfg(feature = "web")] + #[cfg_attr(docsrs, doc(cfg(feature = "web")))] + #[must_use] + pub fn with_web_resolver(mut self, resolver: deno_fetch::dns::Resolver) -> Self { + self.0.extension_options.web.resolver = resolver; + self + } + + /// Consume the builder and create a new runtime with the given options + /// + /// # Errors + /// Will return an error if the runtime cannot be created (usually an issue with extensions) + pub fn build(self) -> Result { + crate::Runtime::new(self.0) + } + + /// Consume the builder and create a new snapshot runtime with the given options + /// + /// # Errors + /// Will return an error if the runtime cannot be created (usually an issue with extensions) + #[cfg(feature = "snapshot_builder")] + #[cfg_attr(docsrs, doc(cfg(feature = "snapshot_builder")))] + pub fn build_snapshot(self) -> Result { + crate::SnapshotBuilder::new(self.0) + } +} + +impl Default for RuntimeBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/src/snapshot_builder.rs b/src/snapshot_builder.rs index 89acb9e..7b7499a 100644 --- a/src/snapshot_builder.rs +++ b/src/snapshot_builder.rs @@ -24,7 +24,7 @@ use crate::{ /// /// # Example /// -/// ```rust +/// ```no_run /// use rustyscript::{SnapshotBuilder, Module, Error}; /// use std::fs; /// @@ -61,7 +61,7 @@ impl SnapshotBuilder { /// A `Result` containing either the initialized runtime instance on success (`Ok`) or an error on failure (`Err`). /// /// # Example - /// ```rust + /// ```no_run /// use rustyscript::{ json_args, Runtime, RuntimeOptions, Module }; /// use std::time::Duration; /// @@ -111,8 +111,8 @@ impl SnapshotBuilder { Ok(Self { inner, tokio }) } - /// Creates a new instance of the runtime with the provided options and a borrowed tokio runtime handle. - /// See [`Runtime::new`] for more information. + /// Creates a new instance of the runtime with the provided options and a borrowed tokio runtime handle. + /// See [`crate::Runtime::new`] for more information. /// /// # Errors /// Can fail if the deno runtime initialization fails (usually issues with extensions) @@ -227,7 +227,7 @@ impl SnapshotBuilder { } /// Remove and return a value from the state, if one exists - /// ```rust + /// ```no_run /// use rustyscript::{ Runtime }; /// /// # fn main() -> Result<(), rustyscript::Error> { @@ -252,7 +252,7 @@ impl SnapshotBuilder { /// # Errors /// Can fail if the inner state cannot be borrowed mutably /// - /// ```rust + /// ```no_run /// use rustyscript::{ Runtime }; /// /// # fn main() -> Result<(), rustyscript::Error> { @@ -446,7 +446,7 @@ impl SnapshotBuilder { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::{ json_args, Runtime, Module, Error }; /// /// # fn main() -> Result<(), Error> { @@ -494,7 +494,7 @@ impl SnapshotBuilder { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::{ json_args, Runtime, Module, Error }; /// /// # fn main() -> Result<(), Error> { @@ -539,7 +539,7 @@ impl SnapshotBuilder { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::{ Runtime, Module, Error }; /// /// # fn main() -> Result<(), Error> { @@ -609,7 +609,7 @@ impl SnapshotBuilder { /// /// # Example /// - /// ```rust + /// ```no_run /// use rustyscript::{ Runtime, Module, Error }; /// /// # fn main() -> Result<(), Error> { @@ -651,7 +651,7 @@ impl SnapshotBuilder { /// /// # Example /// - /// ```rust + /// ```no_run /// // Create a module with filename and contents /// use rustyscript::{Runtime, Module, Error}; /// @@ -717,7 +717,7 @@ impl SnapshotBuilder { /// /// # Example /// - /// ```rust + /// ```no_run /// // Create a module with filename and contents /// use rustyscript::{Runtime, Module, Error}; /// diff --git a/src/static_runtime.rs b/src/static_runtime.rs index 9943fa2..f4e65de 100644 --- a/src/static_runtime.rs +++ b/src/static_runtime.rs @@ -2,7 +2,7 @@ //! It creates a safe, thread-local runtime static. //! //! Can be used with default `RuntimeOptions` like so: -//! ```rust +//! ```no_run //! use rustyscript::{RuntimeOptions, Error, static_runtime}; //! use std::time::Duration; //! @@ -16,7 +16,7 @@ //! ``` //! //! Or with custom `RuntimeOptions`: -//! ```rust +//! ```no_run //! use rustyscript::{Error, RuntimeOptions, static_runtime}; //! use std::time::Duration; //! @@ -63,7 +63,7 @@ impl StaticRuntimeLock<'_> { /// /// # Example /// Can be used with default `RuntimeOptions` like so: -/// ```rust +/// ```no_run /// use rustyscript::{Error, static_runtime}; /// /// static_runtime!(MY_DEFAULT_RUNTIME); @@ -76,7 +76,7 @@ impl StaticRuntimeLock<'_> { /// ``` /// /// Or with custom `RuntimeOptions`: -/// ```rust +/// ```no_run /// use rustyscript::{Error, RuntimeOptions, static_runtime}; /// use std::time::Duration; /// @@ -162,7 +162,7 @@ impl StaticRuntime { /// The second argument is an optional block that should return a `RuntimeOptions` instance /// /// Can be used with default `RuntimeOptions` like so: -/// ```rust +/// ```no_run /// use rustyscript::{RuntimeOptions, Error, static_runtime}; /// use std::time::Duration; /// @@ -176,7 +176,7 @@ impl StaticRuntime { /// ``` /// /// Or with custom `RuntimeOptions`: -/// ```rust +/// ```no_run /// use rustyscript::{Error, RuntimeOptions, static_runtime}; /// use std::time::Duration; /// diff --git a/src/traits.rs b/src/traits.rs index e0e73d6..209ca85 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,9 +1,6 @@ use std::{borrow::Cow, path::Path}; -use deno_core::{ - v8::{self, HandleScope}, - ModuleSpecifier, -}; +use deno_core::{v8, ModuleSpecifier}; use crate::Error; @@ -35,22 +32,6 @@ impl> ToModuleSpecifier for T { } } -pub trait ToV8String { - fn to_v8_string<'a>( - &self, - scope: &mut HandleScope<'a>, - ) -> Result, Error>; -} - -impl ToV8String for str { - fn to_v8_string<'a>( - &self, - scope: &mut HandleScope<'a>, - ) -> Result, Error> { - v8::String::new(scope, self).ok_or(Error::V8Encoding(self.to_string())) - } -} - pub trait ToDefinedValue { fn if_defined(&self) -> Option; } diff --git a/src/utilities.rs b/src/utilities.rs index 85796d9..0db1962 100644 --- a/src/utilities.rs +++ b/src/utilities.rs @@ -22,7 +22,7 @@ use crate::{traits::ToModuleSpecifier, Error, Module, ModuleWrapper, Runtime, Ru /// /// # Example /// -/// ```rust +/// ```no_run /// let result: i64 = rustyscript::evaluate("5 + 5").expect("The expression was invalid!"); /// assert_eq!(10, result); /// ``` @@ -48,7 +48,7 @@ where /// /// # Example /// -/// ```rust +/// ```no_run /// assert!(rustyscript::validate("5 + 5").expect("Something went wrong!")); /// ``` pub fn validate(javascript: &str) -> Result { @@ -98,7 +98,7 @@ pub fn import(path: &str) -> Result { /// /// # Example /// -/// ```rust +/// ```no_run /// rustyscript::resolve_path("test.js", None).expect("Something went wrong!"); /// ``` pub fn resolve_path( @@ -137,7 +137,7 @@ mod runtime_macros { /// You can also just pass a &tuple directly, or an &array, or even a single value /// /// # Example - /// ```rust + /// ```no_run /// use rustyscript::{ Runtime, RuntimeOptions, Module, json_args }; /// use std::time::Duration; /// @@ -149,7 +149,7 @@ mod runtime_macros { /// rustyscript.register_entrypoint(load); /// "); /// - /// Runtime::execute_module( + /// Runtime::execute_module::<()>( /// &module, vec![], /// Default::default(), /// json_args!("test", 5) @@ -174,7 +174,7 @@ mod runtime_macros { /// Benchmarks place the performance difference at nearly 1,000 times slower! /// /// # Example - /// ```rust + /// ```no_run /// use rustyscript::{ Runtime, RuntimeOptions, Module, big_json_args }; /// use std::time::Duration; /// @@ -186,7 +186,7 @@ mod runtime_macros { /// rustyscript.register_entrypoint(load); /// "); /// - /// Runtime::execute_module( + /// Runtime::execute_module::<()>( /// &module, vec![], /// Default::default(), /// big_json_args!("test", 5) @@ -208,7 +208,7 @@ mod runtime_macros { /// Takes care of deserializing arguments and serializing the result /// /// # Example - /// ```rust + /// ```no_run /// use rustyscript::{ Error, sync_callback }; /// let add = sync_callback!( /// |a: i64, b: i64| { @@ -237,7 +237,7 @@ mod runtime_macros { /// Takes care of deserializing arguments and serializing the result /// /// # Example - /// ```rust + /// ```no_run /// use rustyscript::{ Error, async_callback }; /// let add = async_callback!( /// |a: i64, b: i64| async move { diff --git a/src/worker.rs b/src/worker.rs index 57e710f..2c56324 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -1,6 +1,6 @@ //! Provides a worker thread that can be used to run javascript code in a separate thread through a channel pair //! It also provides a default worker implementation that can be used without any additional setup: -//! ```rust +//! ```no_run //! use rustyscript::{Error, worker::{Worker, DefaultWorker, DefaultWorkerOptions}}; //! use std::time::Duration; //!