├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── rustyscript-logo-wide.png └── workflows │ ├── docs.yml │ ├── examples.yml │ ├── linter.yml │ ├── op_whitelist.yml │ └── tests.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── FUNDING.yml ├── LICENSE ├── benches └── runtime.rs ├── examples ├── async_eval.rs ├── async_javascript.rs ├── background_tasks.rs ├── call_rust_from_js.rs ├── create_snapshot.rs ├── custom_import_logic.rs ├── custom_runtimes.rs ├── custom_threaded_worker.rs ├── default_threaded_worker.rs ├── entrypoint_functions.rs ├── example_extension │ ├── example_extension.js │ └── mod.rs ├── functions_and_values.rs ├── hello_world.rs ├── interactive_prompt.rs ├── javascript │ ├── example_module.js │ └── multiple_modules.js ├── max_heap_size.rs ├── module_import.rs ├── module_loader_cache.rs ├── multiple_modules.rs ├── node_import │ ├── main.rs │ ├── node_modules │ │ ├── .package-lock.json │ │ └── chalk │ │ │ ├── license │ │ │ ├── package.json │ │ │ ├── readme.md │ │ │ └── source │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── utilities.js │ │ │ └── vendor │ │ │ ├── ansi-styles │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ │ └── supports-color │ │ │ ├── browser.d.ts │ │ │ ├── browser.js │ │ │ ├── index.d.ts │ │ │ └── index.js │ └── package.json ├── runtime_extensions.rs ├── serialized_types.rs ├── thread_safety.rs ├── typescript_modules.rs ├── url_import.rs ├── web_features.rs ├── websocket.rs └── worker_pool.rs ├── readme.md └── src ├── async_bridge.rs ├── error.rs ├── ext ├── broadcast_channel │ ├── init_broadcast_channel.js │ ├── mod.rs │ └── wrapper.rs ├── cache │ ├── cache_backend.rs │ ├── init_cache.js │ ├── memory.rs │ └── mod.rs ├── console │ ├── init_console.js │ └── mod.rs ├── cron │ ├── init_cron.js │ └── mod.rs ├── crypto │ ├── init_crypto.js │ └── mod.rs ├── ffi │ ├── init_ffi.js │ └── mod.rs ├── fs │ ├── init_fs.js │ └── mod.rs ├── http │ ├── http_runtime.rs │ ├── init_http.js │ └── mod.rs ├── io │ ├── init_io.js │ ├── mod.rs │ ├── tty_unix.rs │ └── tty_windows.rs ├── kv │ ├── init_kv.js │ └── mod.rs ├── mod.rs ├── napi │ ├── init_napi.js │ └── mod.rs ├── node │ ├── cjs_translator.rs │ ├── init_node.js │ ├── mod.rs │ └── resolvers.rs ├── runtime │ ├── init_runtime.js │ └── mod.rs ├── rustyscript │ ├── callbacks.rs │ ├── mod.rs │ └── rustyscript.js ├── url │ ├── init_url.js │ └── mod.rs ├── web │ ├── init_errors.js │ ├── init_fetch.js │ ├── init_net.js │ ├── init_telemetry.js │ ├── init_web.js │ ├── mod.rs │ ├── options.rs │ └── permissions.rs ├── web_stub │ ├── 01_dom_exception.js │ ├── 02_timers.js │ ├── 05_base64.js │ ├── encoding.rs │ ├── init_stub.js │ ├── mod.rs │ └── timers.rs ├── webgpu │ ├── init_webgpu.js │ └── mod.rs ├── webidl │ ├── init_webidl.js │ └── mod.rs ├── websocket │ ├── init_websocket.js │ └── mod.rs └── webstorage │ ├── init_webstorage.js │ └── mod.rs ├── inner_runtime.rs ├── js_value.rs ├── js_value ├── function.rs ├── map.rs ├── promise.rs └── string.rs ├── lib.rs ├── module.rs ├── module_handle.rs ├── module_loader.rs ├── module_loader ├── cache_provider.rs ├── import_provider.rs └── inner_loader.rs ├── module_wrapper.rs ├── op_whitelist.js ├── runtime.rs ├── runtime_builder.rs ├── snapshot_builder.rs ├── static_runtime.rs ├── traits.rs ├── transpiler.rs ├── utilities.rs └── worker.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/rustyscript-logo-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rscarson/rustyscript/1f18c83bc4a32c31f6c34cd07f18d4b0c5ab6cf5/.github/rustyscript-logo-wide.png -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | # By a huge margin the slowest test, so we run it separately 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | # Run tests with all features 22 | - name: Run complete tests 23 | run: cargo test --all-features --doc 24 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | 2 | #### #### 3 | # In this workflow we run all the examples # 4 | # This should always be up-to-date with the examples # 5 | #### #### 6 | name: Examples 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: worker_pool 25 | run: cargo run --example worker_pool --features "worker" 26 | 27 | - name: web_features 28 | run: cargo run --example web_features --features "web" 29 | 30 | - name: url_imports 31 | run: cargo run --example url_import --features="fs_import url_import" 32 | 33 | - name: typescript_modules 34 | run: cargo run --example typescript_modules 35 | 36 | - name: thread_safety 37 | run: cargo run --example thread_safety 38 | 39 | - name: serialized_types 40 | run: cargo run --example serialized_types 41 | 42 | - name: runtime_extensions 43 | run: cargo run --example runtime_extensions 44 | 45 | - name: multiple_modules 46 | run: cargo run --example multiple_modules 47 | 48 | - name: module_loader_cache 49 | run: cargo run --example module_loader_cache 50 | 51 | - name: module_import 52 | run: cargo run --example module_import 53 | 54 | - name: interactive_prompt 55 | run: cargo run --example interactive_prompt -- 5+5 56 | 57 | - name: hello_world 58 | run: cargo run --example hello_world 59 | 60 | - name: functions_and_values 61 | run: cargo run --example functions_and_values 62 | 63 | - name: entrypoint_functions 64 | run: cargo run --example entrypoint_functions 65 | 66 | - name: default_threaded_worker 67 | run: cargo run --example default_threaded_worker --features "worker" 68 | 69 | - name: custom_threaded_worker 70 | run: cargo run --example custom_threaded_worker --features "worker" 71 | 72 | - name: custom_runtimes 73 | run: cargo run --example custom_runtimes 74 | 75 | - name: custom_import_logic 76 | run: cargo run --example custom_import_logic --features "url_import" 77 | 78 | - name: create_snapshot 79 | run: cargo run --example create_snapshot --features "snapshot_builder" 80 | 81 | - name: call_rust_from_js 82 | run: cargo run --example call_rust_from_js 83 | 84 | - name: async_javascript 85 | run: cargo run --example async_javascript 86 | 87 | - name: async_eval 88 | run: cargo run --example async_eval 89 | 90 | - name: websocket 91 | run: cargo run --example websocket --features="web websocket" 92 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | # Check if the README is up to date 21 | # We will do this before all the time-consuming tests 22 | - name: Check if the README is up to date. 23 | run: | 24 | cargo install cargo-rdme 25 | cargo rdme --check 26 | 27 | # Check formatting 28 | - name: Run fmt 29 | run: cargo fmt --check 30 | 31 | # Lint all the things 32 | - name: Run clippy 33 | run: cargo clippy --all-features 34 | 35 | # Test documentation generation 36 | - name: Test documentation 37 | run: cargo doc --features snapshot_builder 38 | -------------------------------------------------------------------------------- /.github/workflows/op_whitelist.yml: -------------------------------------------------------------------------------- 1 | name: Sandbox integrity checks 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Run whitelist check for default-feature provided ops 20 | run: cargo test --lib -- test::check_op_whitelist --exact --show-output 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | # Run tests with no features 21 | - name: Run barebones tests 22 | run: cargo test --no-default-features --lib 23 | 24 | # Run default feature tests 25 | - name: Run tests 26 | run: cargo test --verbose --lib 27 | 28 | # Run tests with just web feature 29 | - name: Run web tests 30 | run: cargo test --features "web" --lib 31 | 32 | # Run tests with all features 33 | - name: Run complete tests 34 | run: cargo test --all-features --lib 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | .idea 4 | snapshot.bin 5 | *.sh -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to rustyscript 2 | 3 | Thank you for your interest in contributing to rustyscript! I appreciate your time, and your help in keeping rustyscript useful for everyone. 4 | 5 | This document will lay out general instructions for several common tasks, as well as the general structure of the project 6 | 7 | ## Adding Deno extensions 8 | The deno extensions require extensive configuration on both the rust and JS sides to function, and have a complex dependency structure that 9 | can easily cause unpredictable issues 10 | 11 | For that reason, rustyscript provides these extensions pre-configured and initialized to the user as crate-level features. 12 | 13 | The process of adding an extension involves the following steps: 14 | 15 | 1. Find the extensions in [deno/ext](https://github.com/denoland/deno/tree/main/ext) 16 | 2. Note the dependencies in `lib.rs`, in the `deps` portion of the call to `deno_core::extension` 17 | 3. Add a new crate-feature for the extension in `Cargo.toml` 18 | 19 | - It should be preceded with a comment linking the portion of the spec it implements (see the extensions's readme) 20 | - It should begin with the extension's crate, which must be an optional dependency of rustyscript 21 | - It should then list the rustyscript crate-features associated with each of the dependencies found in step 2 22 | 23 | 4. Add a new directory in `src/ext/extension_name`. It will contain `mod.rs` and `init_extension_name.js` 24 | 5. Navigate to [deno/runtime/js](https://github.com/denoland/deno/tree/main/runtime/js) and find all the places in global the extension is added 25 | 6. In `init_extension_name.js`, include -all- JS sources provided by the new extension, and add the relevant portions to global (see other exts for examples) 26 | 7. In `mod.rs`, create your `init_extension_name` extensions and provide the `extensions` function 27 | 8. Implement `ExtensionTrait` for the deno_extension and your initializer 28 | 9. If the extension requires configuration, add a feature-gated section to `ExtensionOptions` 29 | 30 | - If only one field, add it directly to ExtensionOptions 31 | - Otherwise add an options structure in `mod.rs` and reference that 32 | - 33 | 10- in `src/ext/mod.rs` add feature-gated imports, and add your extension to the extensions functions -BELOW- any of its dependencies 34 | 11- Add a section to the table in `lib.rs` for the new extension, and use `cargo rdme` to regenerate the readme 35 | 36 | ## Project Structure 37 | 38 | There are several public-facing portions of the project, most notably `runtime`, `js_value`, and `module` 39 | 40 | But there are also portions that should never be public facing: 41 | 42 | ### `inner_runtime::InnerRuntime` 43 | This is the underlying logic that runtime wraps - it should be async-only, and exposed only through `runtime`'s calls out to it 44 | This should be kept private to simplify the APi 45 | 46 | ### `module_loader::RustyLoader` 47 | This is the logic underlying transpilation, fetching module code, generating errors, and security around FS/URL imports 48 | This should be kept private due to the sheer number of ways the crate depends on the behaviour of the loader. 49 | 50 | ### `transpiler` 51 | This is simply a set of utilities for TS -> JS transpilation and is only useful in the context of the module-loader 52 | There is no reason to expose this to the user 53 | 54 | ### `ext::*` 55 | The deno extensions require extensive configuration on both the rust and JS sides to function, and have a complex dependency structure that 56 | can easily cause unpredictable issues 57 | 58 | For that reason, rustyscript provides these extensions pre-configured and initialized to the user as crate-level features. Because we managed load-order 59 | and dependencies between extensions, exposing these could be dangerous. 60 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | buy_me_a_coffee: rscarson -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Richard Carson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the Software), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, andor sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /benches/runtime.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use rustyscript::{json_args, Module, Runtime, RuntimeOptions}; 3 | 4 | fn criterion_benchmark(c: &mut Criterion) { 5 | c.bench_function("init_runtime", |b| { 6 | b.iter(|| Runtime::new(Default::default()).expect("Could not create runtime")) 7 | }); 8 | 9 | let mut runtime = Runtime::new(Default::default()).expect("Could not create runtime"); 10 | let mut m_id = 0; 11 | c.bench_function("load_module", |b| { 12 | b.iter(|| { 13 | let module = Module::new(format!("{m_id}.js"), "export const v = 1;"); 14 | m_id += 1; 15 | runtime.load_module(&module).expect("Could not load mod"); 16 | }) 17 | }); 18 | 19 | // Set up a runtime for the next 2 tests 20 | let mut runtime = Runtime::new(RuntimeOptions { 21 | default_entrypoint: Some("test".to_string()), 22 | ..Default::default() 23 | }) 24 | .expect("Could not create runtime"); 25 | let modref = runtime 26 | .load_module(&Module::new( 27 | "test_entrypoint.js", 28 | " 29 | export function test() { return 1; } 30 | ", 31 | )) 32 | .expect("Could not load mod"); 33 | 34 | c.bench_function("call_entrypoint", |b| { 35 | b.iter(|| { 36 | let _: usize = runtime 37 | .call_entrypoint(&modref, json_args!()) 38 | .expect("could not call function"); 39 | }) 40 | }); 41 | 42 | c.bench_function("call_function", |b| { 43 | b.iter(|| { 44 | let _: usize = runtime 45 | .call_function(Some(&modref), "test", json_args!()) 46 | .expect("could not call function"); 47 | }) 48 | }); 49 | 50 | c.bench_function("call_function_with_args", |b| { 51 | b.iter(|| { 52 | let _: usize = runtime 53 | .call_function(Some(&modref), "test", json_args!("test", 1, false)) 54 | .expect("could not call function"); 55 | }) 56 | }); 57 | } 58 | 59 | criterion_group!(benches, criterion_benchmark); 60 | criterion_main!(benches); 61 | -------------------------------------------------------------------------------- /examples/async_eval.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows how to use `Runtime::eval` to run async code 3 | /// Note that there is no support for top-level await but you can use `Promise` to work around this 4 | /// 5 | use rustyscript::{js_value::Promise, Error, Runtime}; 6 | 7 | fn main() -> Result<(), Error> { 8 | // Create a new runtime 9 | let mut runtime = Runtime::new(Default::default())?; 10 | let tokio_runtime = runtime.tokio_runtime(); 11 | 12 | // A little setup for later 13 | // The `::<()>` is a type hint to the compiler that we don't need a return value 14 | // Previously it could be left out, but now it will cause a warning, and in the future an error 15 | runtime.eval::<()>( 16 | "globalThis.sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));", 17 | )?; 18 | 19 | // Can be run as blocking 20 | runtime.eval::("sleep(1000).then(() => 1)")?; 21 | 22 | // Or as async 23 | let future = async { 24 | let result: Promise = runtime.eval_immediate("sleep(1000).then(() => 2)").await?; 25 | result.into_future(&mut runtime).await?; 26 | 27 | Ok::<(), Error>(()) 28 | }; 29 | tokio_runtime.block_on(future)?; 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /examples/async_javascript.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows the use of async functions in JS 3 | /// 4 | /// Here we have a module with 4 async functions that resolve after a given time 5 | /// We call them in sequence and await the results 6 | /// 7 | /// Notes: 8 | /// - When using the async variants of the functions it is important to complete the JS event loop with [Runtime::await_event_loop] 9 | /// - Async variants will wait for the function's return value to resolve, but will not wait for the event loop to complete 10 | /// 11 | use rustyscript::{js_value::Promise, json_args, Error, Module, Runtime}; 12 | 13 | fn main() -> Result<(), Error> { 14 | let module = Module::new( 15 | "test.js", 16 | " 17 | function resolve_after(t) { 18 | return new Promise((resolve) => { 19 | return setTimeout(() => { 20 | console.log('Finished after ' + t + 'ms'); 21 | resolve('resolved'); 22 | }, t); 23 | }); 24 | } 25 | export const f1 = async () => resolve_after(4000); 26 | export const f2 = async () => resolve_after(2000); 27 | export const f3 = async () => resolve_after(1000); 28 | ", 29 | ); 30 | 31 | // Create a new runtime 32 | let mut runtime = Runtime::new(Default::default())?; 33 | let handle = runtime.load_module(&module)?; 34 | let tokio_runtime = runtime.tokio_runtime(); 35 | 36 | // 37 | // In this version we await the promises in sequence 38 | // They will be resolved in the order they were called 39 | // 40 | let future = async { 41 | let v1: String = runtime 42 | .call_function_async(Some(&handle), "f1", json_args!()) 43 | .await?; 44 | let v2: String = runtime 45 | .call_function_async(Some(&handle), "f2", json_args!()) 46 | .await?; 47 | let v3: String = runtime 48 | .call_function_async(Some(&handle), "f3", json_args!()) 49 | .await?; 50 | 51 | println!("v1={}\nv2={}\nv3={}", v1, v2, v3); 52 | 53 | Ok::<(), Error>(()) 54 | }; 55 | tokio_runtime.block_on(future)?; 56 | 57 | // Another way to do it is to export the promises first 58 | // Then await them in sequence after running the event loop 59 | // This is useful when you need to store the promises for later 60 | // 61 | // Normally calling a function would resolve the promise, 62 | // Causing the future to borrow the runtime mutably 63 | // So in order to store all the promises at once, we need to call `call_function_immediate` 64 | // Which will not resolve the event loop 65 | // 66 | // We use a future here because the function uses setTimeout, which must be 67 | // run from inside a tokio runtime 68 | let future = async { 69 | let p1: Promise = 70 | runtime.call_function_immediate(Some(&handle), "f1", json_args!())?; 71 | let p2: Promise = 72 | runtime.call_function_immediate(Some(&handle), "f2", json_args!())?; 73 | let p3: Promise = 74 | runtime.call_function_immediate(Some(&handle), "f3", json_args!())?; 75 | Ok::<_, Error>((p1, p2, p3)) 76 | }; 77 | let (p1, p2, p3) = tokio_runtime.block_on(future)?; 78 | 79 | // Now we can convert the promises back into futures 80 | // And await them in sequence 81 | // Converting them into the actual values we want 82 | let future = async { 83 | println!( 84 | "p1={}\np2={}\np3={}", 85 | p1.into_future(&mut runtime).await?, 86 | p2.into_future(&mut runtime).await?, 87 | p3.into_future(&mut runtime).await?, 88 | ); 89 | 90 | Ok::<(), Error>(()) 91 | }; 92 | tokio_runtime.block_on(future)?; 93 | 94 | Ok(()) 95 | } 96 | -------------------------------------------------------------------------------- /examples/background_tasks.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use deno_core::PollEventLoopOptions; 4 | /// 5 | /// This example the use of async module loading, and the handing of ongoing 6 | /// background tasks. 7 | /// 8 | use rustyscript::{Error, Module, ModuleHandle, Runtime, RuntimeOptions}; 9 | 10 | fn main() -> Result<(), Error> { 11 | let module = Module::new( 12 | "test.js", 13 | " 14 | // A basic messaging queue 15 | const messages = []; 16 | export function nextMessage() { 17 | if (messages.length === 0) { 18 | return ''; 19 | } 20 | return messages.shift(); 21 | } 22 | 23 | const socket = new WebSocket('wss://echo.websocket.org'); 24 | export function sendMessage(text) { 25 | socket.send(text); 26 | } 27 | 28 | socket.addEventListener('error', (e) => { 29 | clearInterval(t); 30 | throw(e); 31 | }); 32 | 33 | socket.addEventListener('open', (event) => { 34 | console.log('Open'); 35 | socket.send('Socket Open!'); 36 | 37 | setTimeout(() => { 38 | // Send a message after 5 seconds 39 | sendMessage('ping'); 40 | }, 5000); 41 | 42 | setTimeout(() => { 43 | // Send a message after 10 seconds 44 | sendMessage('pong'); 45 | }, 10000); 46 | 47 | setTimeout(() => { 48 | // Close the socket after 15 seconds 49 | socket.close(); 50 | 51 | // Clear the interval, ending the event loop 52 | console.log('Closing socket'); 53 | clearInterval(t); 54 | }, 15000); 55 | }); 56 | 57 | socket.addEventListener('message', (event) => { 58 | console.log('Received a message'); 59 | messages.push(event.data); 60 | }); 61 | 62 | // Keep the event loop alive 63 | let t = setInterval(() => { 64 | }, 1000); 65 | ", 66 | ); 67 | 68 | // Whitelist the echo server for certificate errors 69 | let mut options = RuntimeOptions::default(); 70 | options 71 | .extension_options 72 | .web 73 | .whitelist_certificate_for("echo.websocket.org"); 74 | 75 | let mut runtime = Runtime::new(options)?; 76 | let tokio_runtime = runtime.tokio_runtime(); 77 | 78 | // Load the module 79 | // This will run the event loop until the module is fully loaded, or an error occurs 80 | let module_handle = tokio_runtime.block_on(runtime.load_module_async(&module))?; 81 | 82 | // Run the event loop until it reports that it has finished 83 | while runtime.advance_event_loop(PollEventLoopOptions::default())? { 84 | // Check for messages from the module 85 | if let Some(msg) = check_for_messages(&mut runtime, &module_handle)? { 86 | println!("Received message: {}", msg); 87 | } 88 | 89 | // Run the event loop for 50ms 90 | runtime.block_on_event_loop( 91 | PollEventLoopOptions::default(), 92 | Some(Duration::from_millis(50)), 93 | )?; 94 | } 95 | 96 | Ok(()) 97 | } 98 | 99 | fn check_for_messages( 100 | rt: &mut Runtime, 101 | module_handle: &ModuleHandle, 102 | ) -> Result, Error> { 103 | let next_message: String = 104 | rt.call_function_immediate(Some(module_handle), "nextMessage", &())?; 105 | if next_message.is_empty() { 106 | Ok(None) 107 | } else { 108 | Ok(Some(next_message)) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/call_rust_from_js.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example is meant to demonstrate the use of the runtime state, as well as the 3 | /// registration of rust functions that are callable from JS 4 | /// 5 | use rustyscript::{async_callback, serde_json, sync_callback, Error, Module, Runtime}; 6 | 7 | fn main() -> Result<(), Error> { 8 | // Let's get a new runtime first 9 | let mut runtime = Runtime::new(Default::default())?; 10 | 11 | // We can use a normal function, if we wish 12 | // It can also be `move` if we want to capture some state 13 | runtime.register_function("echo", |args| { 14 | // Decode the input 15 | let input = args 16 | .first() 17 | .ok_or(Error::Runtime("No input".to_string())) 18 | .map(|v| serde_json::from_value::(v.clone()))??; 19 | 20 | // Encode the output 21 | let output = format!("Hello, {input}!"); 22 | Ok::<_, Error>(serde_json::Value::String(output)) 23 | })?; 24 | 25 | // There is also a helper macro to create a callback 26 | // It will take care of deserializing arguments and serializing the result 27 | runtime.register_function( 28 | "add", 29 | sync_callback!(|a: i64, b: i64| { 30 | a.checked_add(b) 31 | .ok_or(Error::Runtime("Overflow".to_string())) 32 | }), 33 | )?; 34 | 35 | // There is also an async version 36 | runtime.register_async_function( 37 | "asyncEcho", 38 | async_callback!(|input: String| { 39 | async move { Ok::<_, Error>(format!("Hello, {input}!")) } 40 | }), 41 | )?; 42 | 43 | // A module that will consume the functions we registered 44 | 45 | // Our module will simply call a rust-side function 46 | let module = Module::new( 47 | "test.js", 48 | " 49 | 50 | let echo = rustyscript.functions['echo']; 51 | let add = rustyscript.functions['add']; 52 | let asyncEcho = rustyscript.async_functions['asyncEcho']; 53 | 54 | console.log(echo('world')); 55 | console.log(add(5, 6)); 56 | asyncEcho('foo').then(console.log); 57 | ", 58 | ); 59 | 60 | // Now we call the function from JS and make sure everything worked 61 | // I'll make any errors prettier here 62 | match runtime.load_module(&module) { 63 | Ok(_) => (), 64 | Err(e) => { 65 | eprintln!("{}", e.as_highlighted(Default::default())); 66 | } 67 | } 68 | 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /examples/create_snapshot.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example demonstrates how to create a snapshot from a module and save it to a file. 3 | /// Snapshots can be used to massively decrease the startup time of a Runtime instance (15ms -> 3ms) by pre-loading 4 | /// extensions and modules into the runtime state before it is created. A snapshot can be used on any runtime with 5 | /// the same set of extensions and options as the runtime that created it. 6 | /// 7 | use rustyscript::{Error, Module, SnapshotBuilder}; 8 | use std::fs; 9 | 10 | fn main() -> Result<(), Error> { 11 | // A module we want pre-loaded into the snapshot 12 | let module = Module::new( 13 | "my_module.js", 14 | "export function importantFunction() { return 42; }", 15 | ); 16 | 17 | // Create a snapshot with default runtime options 18 | // These options need to be the same as the ones used to create the runtime 19 | let snapshot = SnapshotBuilder::new(Default::default())? 20 | .with_module(&module)? 21 | .finish(); 22 | 23 | // Save the snapshot to a file 24 | fs::write("snapshot.bin", snapshot)?; 25 | Ok(()) 26 | 27 | // To use the snapshot, load it with `include_bytes!` into the `RuntimeOptions` struct: 28 | // const STARTUP_SNAPSHOT: &[u8] = include_bytes!("snapshot.bin"); 29 | // let options = RuntimeOptions { 30 | // startup_snapshot: Some(STARTUP_SNAPSHOT), 31 | // ..Default::default() 32 | // }; 33 | } 34 | -------------------------------------------------------------------------------- /examples/custom_import_logic.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! In this example I will demonstrate how to override the default import logic of the runtime by implementing a custom 3 | //! import provider. 4 | //! 5 | //! In this case, I will allowing for two new import schemes: 6 | //! - `static:`: This scheme will allow for static modules to be imported by their specifier 7 | //! - `redirect:`: This scheme will allow for modules to be redirected to a different specifier 8 | //! 9 | use deno_core::{error::ModuleLoaderError, ModuleSpecifier}; 10 | use rustyscript::{module_loader::ImportProvider, Module, Runtime, RuntimeOptions}; 11 | use std::collections::HashMap; 12 | 13 | /// A custom import provider that allows for static modules and redirects 14 | #[derive(Default)] 15 | struct MyImportProvider { 16 | static_modules: HashMap, 17 | redirects: HashMap, 18 | } 19 | impl MyImportProvider { 20 | // 21 | // The schemes we will be using 22 | const STATIC_SCHEME: &'static str = "static"; 23 | const REDIRECT_SCHEME: &'static str = "redirect"; 24 | 25 | /// Add a static module to the provider 26 | fn add_static_module(&mut self, specifier: &str, source: &str) { 27 | self.static_modules 28 | .insert(specifier.to_string(), source.to_string()); 29 | } 30 | 31 | /// Add a redirect to the provider 32 | fn add_redirect(&mut self, from: &str, to: &str) -> Result<(), deno_core::error::AnyError> { 33 | self.redirects 34 | .insert(from.to_string(), ModuleSpecifier::parse(to)?); 35 | Ok(()) 36 | } 37 | } 38 | 39 | impl ImportProvider for MyImportProvider { 40 | fn resolve( 41 | &mut self, 42 | specifier: &ModuleSpecifier, 43 | _referrer: &str, 44 | _kind: deno_core::ResolutionKind, 45 | ) -> Option> { 46 | match specifier.scheme() { 47 | // 48 | // static:*, use the static module set 49 | Self::STATIC_SCHEME => { 50 | if self.static_modules.contains_key(specifier.path()) { 51 | // Import is allowed - return the specifier 52 | Some(Ok(specifier.clone())) 53 | } else { 54 | // Not found - deny the import 55 | Some(Err(ModuleLoaderError::NotFound)) 56 | } 57 | } 58 | 59 | // 60 | // redirect:*, use the redirect set 61 | Self::REDIRECT_SCHEME => { 62 | if let Some(redirected_specifier) = self.redirects.get(specifier.path()) { 63 | // Redirected - return the redirected specifier 64 | Some(Ok(redirected_specifier.clone())) 65 | } else { 66 | // No redirect, deny the import 67 | Some(Err(ModuleLoaderError::NotFound)) 68 | } 69 | } 70 | 71 | // Not in scope for us, let the standard loader handle it 72 | _ => None, 73 | } 74 | } 75 | 76 | fn import( 77 | &mut self, 78 | specifier: &ModuleSpecifier, 79 | _referrer: Option<&ModuleSpecifier>, 80 | _is_dyn_import: bool, 81 | _requested_module_type: deno_core::RequestedModuleType, 82 | ) -> Option> { 83 | match specifier.scheme() { 84 | // 85 | // static:*, use the static module set 86 | Self::STATIC_SCHEME => { 87 | if let Some(source) = self.static_modules.get(specifier.path()) { 88 | // Found, return the source 89 | Some(Ok(source.clone())) 90 | } else { 91 | // Not found, deny the import 92 | Some(Err(ModuleLoaderError::NotFound)) 93 | } 94 | } 95 | 96 | // 97 | // Let the standard loader handle redirected specifiers 98 | _ => None, 99 | } 100 | } 101 | } 102 | 103 | fn main() -> Result<(), rustyscript::Error> { 104 | let mut import_provider = MyImportProvider::default(); 105 | import_provider.add_redirect("mod_assert", "https://deno.land/std@0.224.0/assert/mod.ts")?; 106 | import_provider.add_static_module("my-module", "export const foo = 1"); 107 | 108 | let mut runtime = Runtime::new(RuntimeOptions { 109 | import_provider: Some(Box::new(import_provider)), 110 | ..Default::default() 111 | })?; 112 | 113 | let module = Module::new( 114 | "custom_imports.js", 115 | " 116 | import { assertEquals } from 'redirect:mod_assert'; 117 | import { foo } from 'static:my-module'; 118 | 119 | assertEquals(1, foo) 120 | ", 121 | ); 122 | 123 | runtime.load_module(&module)?; 124 | Ok(()) 125 | } 126 | -------------------------------------------------------------------------------- /examples/custom_runtimes.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example demonstrates extending Runtime to inline your own extensions and modules 3 | /// as well as enforce values for the Runtime's options 4 | /// 5 | /// This example creates a runtime which will timeout after 0.5s, imports an exension, 6 | /// And ensures that a preset module is always available for import. 7 | /// 8 | /// Extensions like the one being used (see examples/ext/example_extension.rs) 9 | /// allow you to call rust code from within JS 10 | /// 11 | /// Extensions consist of a set of #[op2] functions, an extension! macro, 12 | /// and one or more optional JS modules. 13 | /// 14 | use rustyscript::{module, Error, Module, ModuleHandle, Runtime, RuntimeOptions}; 15 | use std::time::Duration; 16 | 17 | // See example_extension for a demonstration 18 | // of creating a deno_core extension for the runtime 19 | mod example_extension; 20 | 21 | // A module that will always be loaded into the custom runtime 22 | const MY_MODULE: Module = module!( 23 | "my_module.js", 24 | "export function importantFunction() { 25 | return 42; 26 | }" 27 | ); 28 | 29 | /// A runtime which will timeout after 0.5s, imports an exension, 30 | /// And ensures that a preset module is always available for import. 31 | pub struct MyRuntime(Runtime); 32 | impl MyRuntime { 33 | /// Create a new instance of the runtime 34 | pub fn new() -> Result { 35 | let mut runtime = Self(Runtime::new(RuntimeOptions { 36 | extensions: vec![example_extension::example_extension::init_ops_and_esm()], 37 | timeout: Duration::from_millis(500), 38 | ..Default::default() 39 | })?); 40 | runtime.load_module(&MY_MODULE)?; 41 | 42 | Ok(runtime) 43 | } 44 | 45 | /// Calls a javascript function within the Deno runtime by its name and deserializes its return value. 46 | /// 47 | /// # Arguments 48 | /// * `name` - A string representing the name of the javascript function to call. 49 | pub fn call_function( 50 | &mut self, 51 | module_context: &ModuleHandle, 52 | name: &str, 53 | args: &impl serde::ser::Serialize, 54 | ) -> Result 55 | where 56 | T: deno_core::serde::de::DeserializeOwned, 57 | { 58 | self.0.call_function(Some(module_context), name, args) 59 | } 60 | 61 | /// Get a value from a runtime instance 62 | /// 63 | /// # Arguments 64 | /// * `name` - A string representing the name of the value to find 65 | pub fn get_value(&mut self, module_context: &ModuleHandle, name: &str) -> Result 66 | where 67 | T: serde::de::DeserializeOwned, 68 | { 69 | self.0.get_value(Some(module_context), name) 70 | } 71 | 72 | /// Executes the given module, and returns a handle allowing you to extract values 73 | /// And call functions 74 | /// 75 | /// # Arguments 76 | /// * `module` - A `Module` object containing the module's filename and contents. 77 | pub fn load_module(&mut self, module: &Module) -> Result { 78 | self.0.load_module(module) 79 | } 80 | } 81 | 82 | fn main() -> Result<(), Error> { 83 | let module = Module::new( 84 | "test.js", 85 | " 86 | import * as myModule from './my_module.js'; 87 | export const value = myModule.importantFunction(); 88 | ", 89 | ); 90 | 91 | let mut runtime = MyRuntime::new()?; 92 | let module_context = runtime.load_module(&module)?; 93 | let value: i32 = runtime.get_value(&module_context, "value")?; 94 | assert_eq!(42, value); 95 | 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /examples/custom_threaded_worker.rs: -------------------------------------------------------------------------------- 1 | use deno_core::serde_json; 2 | /// 3 | /// This example shows how to use the threaded worker feature using a custom implementation of a worker 4 | /// We will create a basic worker implementation able to execute snippets of non-emca JS 5 | /// 6 | use rustyscript::{ 7 | worker::{InnerWorker, Worker}, 8 | Error, Runtime, 9 | }; 10 | 11 | fn main() -> Result<(), Error> { 12 | let worker = MyWorker::new(MyWorkerOptions { 13 | timeout: std::time::Duration::from_secs(1), 14 | })?; 15 | 16 | let result: i32 = worker.execute("1 + 2")?; 17 | assert_eq!(result, 3); 18 | 19 | Ok(()) 20 | } 21 | 22 | /// The worker implementation 23 | /// We will have instances supertype the Worker itself 24 | /// so can just instantiate this struct directly 25 | pub struct MyWorker(Worker); 26 | 27 | impl MyWorker { 28 | /// Create a new instance of the worker 29 | pub fn new(options: MyWorkerOptions) -> Result { 30 | Ok(Self(Worker::new(options)?)) 31 | } 32 | 33 | /// Execute a snippet of JS code on our threaded worker 34 | pub fn execute(&self, code: &str) -> Result 35 | where 36 | T: serde::de::DeserializeOwned, 37 | { 38 | match self 39 | .0 40 | .send_and_await(MyWorkerMessage::Execute(code.to_string()))? 41 | { 42 | MyWorkerMessage::Value(v) => Ok(serde_json::from_value(v)?), 43 | MyWorkerMessage::Error(e) => Err(e), 44 | _ => Err(Error::Runtime("Unexpected response".to_string())), 45 | } 46 | } 47 | } 48 | 49 | /// The messages we will use to communicate with the worker 50 | pub enum MyWorkerMessage { 51 | Execute(String), 52 | 53 | Error(Error), 54 | Value(serde_json::Value), 55 | } 56 | 57 | /// The runtime options for our worker 58 | #[derive(Clone)] 59 | pub struct MyWorkerOptions { 60 | pub timeout: std::time::Duration, 61 | } 62 | 63 | // Our implementation of the InnerWorker trait 64 | // This is where we define how the worker will handle queries 65 | // Here we are using the same message type for queries and responses 66 | // and using the default runtime 67 | impl InnerWorker for MyWorker { 68 | type Query = MyWorkerMessage; 69 | type Response = MyWorkerMessage; 70 | type RuntimeOptions = MyWorkerOptions; 71 | type Runtime = Runtime; 72 | 73 | /// Initialize the runtime using the options provided 74 | fn init_runtime(options: Self::RuntimeOptions) -> Result { 75 | Runtime::new(rustyscript::RuntimeOptions { 76 | timeout: options.timeout, 77 | ..Default::default() 78 | }) 79 | } 80 | 81 | /// Handle all possible queries 82 | fn handle_query(runtime: &mut Self::Runtime, query: Self::Query) -> Self::Response { 83 | match query { 84 | MyWorkerMessage::Execute(code) => match runtime.eval::(&code) { 85 | Ok(value) => MyWorkerMessage::Value(value), 86 | Err(e) => MyWorkerMessage::Error(e), 87 | }, 88 | 89 | MyWorkerMessage::Error(e) => MyWorkerMessage::Error(e), 90 | MyWorkerMessage::Value(v) => MyWorkerMessage::Value(v), 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /examples/default_threaded_worker.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows how to use the threaded worker feature using the default worker implementation 3 | /// In this example we load a module, and execute a function from it 4 | /// 5 | use rustyscript::{worker::DefaultWorker, Error, Module}; 6 | 7 | fn main() -> Result<(), Error> { 8 | let worker = DefaultWorker::new(Default::default())?; 9 | 10 | let module = Module::new("test.js", "export function add(a, b) { return a + b; }"); 11 | let module_id = worker.load_module(module)?; 12 | 13 | let result: i32 = 14 | worker.call_function(Some(module_id), "add".to_string(), vec![1.into(), 2.into()])?; 15 | assert_eq!(result, 3); 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /examples/entrypoint_functions.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example is meant to demonstrate the basic usage of entrypoint functions 3 | /// 4 | /// A module can optionally have an entrypoint function (that can return a value and accept args) 5 | /// which can be called from rust on load. 6 | /// 7 | /// The same effect can be achieved by calling a function later, so they are optional 8 | /// They are most useful in the context of Runtime::execute_module, which can be seen 9 | /// in the 'hello_world' example. 10 | /// 11 | use rustyscript::{json_args, Error, Module, Runtime, RuntimeOptions, Undefined}; 12 | 13 | fn main() -> Result<(), Error> { 14 | let module = Module::new( 15 | "test.js", 16 | " 17 | let internalValue = 0; 18 | export const getValue = () => internalValue; 19 | export function setUp(value) { 20 | internalValue = value * 2; 21 | } 22 | ", 23 | ); 24 | 25 | // Let's get a new runtime that defaults to the setUp function as the entrypoint 26 | // and load our ES module into it 27 | let mut runtime = Runtime::new(RuntimeOptions { 28 | default_entrypoint: Some("setUp".to_string()), 29 | ..Default::default() 30 | })?; 31 | let module_handle = runtime.load_module(&module)?; 32 | 33 | // We call the entrypoint - Undefined just means we don't care about 34 | // the return type here 35 | runtime.call_entrypoint::(&module_handle, json_args!(2))?; 36 | 37 | // Now the setUp is done, and the internal value is ready for use 38 | let internal_value: usize = 39 | runtime.call_function(Some(&module_handle), "getValue", json_args!())?; 40 | assert_eq!(4, internal_value); 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /examples/example_extension/example_extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is the JS component used in the runtime_extensions example 3 | * It exports a function that calls the op_add_example Rust function 4 | */ 5 | 6 | export const add = (a, b) => Deno.core.ops.op_add_example(a, b); 7 | -------------------------------------------------------------------------------- /examples/example_extension/mod.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows the definition of a simple extension exposing one op 3 | /// Also see example_extension.js 4 | /// 5 | /// Extensions like the one below allow you to let JS code call functions 6 | /// in rust 7 | /// 8 | /// Extensions consist of a set of #[op2] functions, an extension! macro, 9 | /// and one or more optional JS modules. 10 | /// 11 | /// 12 | use rustyscript::deno_core::{extension, op2}; 13 | 14 | #[op2(fast)] 15 | #[bigint] 16 | fn op_add_example(#[bigint] a: i64, #[bigint] b: i64) -> i64 { 17 | a + b 18 | } 19 | 20 | extension!( 21 | example_extension, 22 | ops = [op_add_example], 23 | esm_entry_point = "example:calculator", 24 | esm = [ dir "examples/example_extension", "example:calculator" = "example_extension.js" ], 25 | ); 26 | -------------------------------------------------------------------------------- /examples/functions_and_values.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example demonstrates how to extract values, and call functions 3 | /// from rust into JS 4 | /// 5 | /// The sample below extracts a value which is deserialized to a custom struct 6 | /// as well as calling a function in JS from rust 7 | /// 8 | use rustyscript::{deno_core::serde::Deserialize, json_args, Error, Module, Runtime}; 9 | 10 | #[derive(PartialEq, Debug, Deserialize)] 11 | struct MyStruct { 12 | name: String, 13 | value: usize, 14 | } 15 | 16 | fn main() -> Result<(), Error> { 17 | let module = Module::new( 18 | "test.js", 19 | " 20 | export function test(value) { 21 | return `foo: ${value}`; 22 | } 23 | 24 | export const bar = { 25 | 'name': 'test', 26 | 'value': 7 27 | }; 28 | ", 29 | ); 30 | 31 | // Creation of a new runtime, using the default options 32 | let mut runtime = Runtime::new(Default::default())?; 33 | 34 | // Import the module 35 | // This returns a handle which is used to contextualize future calls 36 | // This ensures you get access to the exports for the module 37 | let module_handle = runtime.load_module(&module)?; 38 | 39 | // Calling an exported function 40 | // This will also work with anything in the global scope (eg: globalThis) 41 | let function_value: String = 42 | runtime.call_function(Some(&module_handle), "test", json_args!("A"))?; 43 | assert_eq!(function_value, "foo: A"); 44 | 45 | // Custom types can be exported from JS easily! 46 | let value: MyStruct = runtime.get_value(Some(&module_handle), "bar")?; 47 | assert_eq!( 48 | MyStruct { 49 | name: "test".to_string(), 50 | value: 7 51 | }, 52 | value 53 | ); 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows a basic usage of the runtime 3 | /// 4 | /// The call to Runtime::execute_module is a one liner which: 5 | /// - Creates a new runtime with the given options 6 | /// - Loads the modules passed in 7 | /// - Calls the module's entrypoint function 8 | /// - Either a default passed as an option 9 | /// - Or the module's default export 10 | /// - Or a call in JS to rustyscript.register_entrypoint 11 | /// - Returns the result 12 | /// 13 | /// Instead of just vec![], one could pass in other JS modules 14 | /// which could be imported using `import './filename.js';` 15 | /// 16 | use rustyscript::{json_args, Error, Module, Runtime}; 17 | 18 | fn main() -> Result<(), Error> { 19 | let module = Module::new( 20 | "test.js", 21 | " 22 | export default (string, integer) => { 23 | console.log(`Hello world: string=${string}, integer=${integer}`); 24 | return 4; 25 | } 26 | ", 27 | ); 28 | 29 | // Execute the module above as an ES module 30 | // Do not side-load any additional modules 31 | // Use the default Runtime options 32 | // Pass 2 args into the entrypoint function 33 | // And expect a usize back from it 34 | let value: usize = 35 | Runtime::execute_module(&module, vec![], Default::default(), json_args!("test", 5))?; 36 | 37 | assert_eq!(value, 4); 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/interactive_prompt.rs: -------------------------------------------------------------------------------- 1 | use rustyscript::{Runtime, RuntimeOptions}; 2 | 3 | fn main() { 4 | interactive_prompt() 5 | } 6 | 7 | fn interactive_prompt() { 8 | // Preload command stack from arguments 9 | let mut stack: Vec = std::env::args().skip(1).collect(); 10 | if stack.is_empty() { 11 | println!("Ready! Type expressions below!"); 12 | } else { 13 | stack.insert(0, "exit".to_string()); 14 | } 15 | 16 | let mut runtime = Runtime::new(RuntimeOptions { 17 | ..Default::default() 18 | }) 19 | .expect("Failed to create runtime"); 20 | 21 | loop { 22 | // Make sure we have a command ready 23 | if stack.is_empty() { 24 | stack.push(next_command()); 25 | } 26 | let cmd = stack.pop().unwrap(); 27 | 28 | if cmd.is_empty() { 29 | continue; 30 | } else if ["exit", "quit"].contains(&cmd.as_str()) { 31 | break; 32 | } else { 33 | // Process the next command 34 | let input = cmd.trim(); 35 | match runtime.eval::(input) { 36 | Ok(value) => println!("{}\n", value), 37 | Err(e) => eprintln!("{}\n", e.as_highlighted(Default::default())), 38 | } 39 | } 40 | } 41 | } 42 | 43 | fn next_command() -> String { 44 | let mut input = String::new(); 45 | print!("> "); 46 | let _ = std::io::Write::flush(&mut std::io::stdout()); 47 | 48 | loop { 49 | std::io::stdin() 50 | .read_line(&mut input) 51 | .expect("error: unable to read user input"); 52 | if !input.trim().ends_with('\\') || input.trim().ends_with("\\\\") { 53 | break; 54 | } 55 | } 56 | 57 | input.trim().to_string() 58 | } 59 | 60 | #[derive(serde::Deserialize, serde::Serialize)] 61 | #[serde(untagged)] 62 | enum ResponseType { 63 | String(String), 64 | Float(f64), 65 | Int(i64), 66 | Bool(bool), 67 | Array(Vec), 68 | Map(std::collections::HashMap), 69 | Null, 70 | } 71 | 72 | impl std::fmt::Display for ResponseType { 73 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 74 | match self { 75 | ResponseType::String(s) => write!(f, "{}", s)?, 76 | ResponseType::Float(n) => write!(f, "{}", n)?, 77 | ResponseType::Int(n) => write!(f, "{}", n)?, 78 | ResponseType::Bool(b) => write!(f, "{}", b)?, 79 | ResponseType::Null => write!(f, "")?, 80 | 81 | ResponseType::Array(a) => { 82 | write!(f, "[")?; 83 | let parts = a 84 | .iter() 85 | .map(|x| format!("{x:?}")) 86 | .collect::>() 87 | .join(", "); 88 | write!(f, "{}]", parts)?; 89 | } 90 | 91 | ResponseType::Map(m) => { 92 | write!(f, "{{")?; 93 | let parts = m 94 | .iter() 95 | .map(|(k, v)| format!("{k}: {v:?}")) 96 | .collect::>() 97 | .join(", "); 98 | write!(f, "{}", parts)?; 99 | write!(f, "}}")?; 100 | } 101 | } 102 | Ok(()) 103 | } 104 | } 105 | 106 | impl std::fmt::Debug for ResponseType { 107 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 108 | match self { 109 | ResponseType::String(s) => { 110 | // Escape string 111 | let s = s 112 | .replace('\n', "\\n") 113 | .replace('\r', "\\r") 114 | .replace('\t', "\\t") 115 | .replace('\"', "\\\""); 116 | write!(f, "\"{}\"", s)?; 117 | } 118 | 119 | ResponseType::Null => write!(f, "")?, 120 | 121 | _ => write!(f, "{self}")?, 122 | } 123 | Ok(()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/javascript/example_module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is a module that gets imported for use in the module_import example 3 | * it includes several functions and constants that get exported for use 4 | */ 5 | 6 | export const MY_FAVOURITE_FOOD = 'saskatoonberries'; 7 | 8 | let book_list = []; 9 | export function addBook(title) { 10 | book_list.push(title) 11 | } 12 | export function listBooks() { 13 | return book_list; 14 | } -------------------------------------------------------------------------------- /examples/javascript/multiple_modules.js: -------------------------------------------------------------------------------- 1 | import * as get_value from '../get_value.ts'; 2 | 3 | // We will get the value set up for us by the runtime, and transform it 4 | // into a string! 5 | let value = get_value.getValue(); 6 | export const final_value = `${value.toFixed(2)}`; -------------------------------------------------------------------------------- /examples/max_heap_size.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows how to set the maximum heap size for the V8 isolate. 3 | /// This is useful when you want to limit the amount of memory a script can consume. 4 | /// A `HeapExhausted` error will be returned if the script exceeds the limit. 5 | /// 6 | use rustyscript::{Error, Module, Runtime, RuntimeOptions}; 7 | 8 | fn main() -> Result<(), Error> { 9 | // Will exceed the defined heap size 10 | let module = Module::new( 11 | "test.js", 12 | "const largeArray = new Array(40 * 1024 * 1024).fill('a');", 13 | ); 14 | 15 | let mut runtime = Runtime::new(RuntimeOptions { 16 | max_heap_size: Some(5 * 1024 * 1024), 17 | ..Default::default() 18 | })?; 19 | 20 | // Will return a `HeapExhausted` error 21 | let module_handle = runtime.load_module(&module); 22 | 23 | assert!(module_handle.is_err()); 24 | 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /examples/module_import.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example is meant to demonstrate the use of the import utility 3 | /// 4 | /// It acts as a wrapper around a runtime with a single loaded module 5 | /// and is meant to simplify usecases where multiple JS sources isn't 6 | /// needed 7 | /// 8 | use rustyscript::{js_value::Function, json_args, Error, Undefined}; 9 | 10 | fn main() -> Result<(), Error> { 11 | let mut module = rustyscript::import("examples/javascript/example_module.js")?; 12 | 13 | // We can list all of this module's exports 14 | assert_eq!( 15 | module.keys(), 16 | vec!["MY_FAVOURITE_FOOD", "addBook", "listBooks"] 17 | ); 18 | 19 | // Or ensure a given export is a function 20 | assert!(module.is_callable("addBook")); 21 | 22 | // We can grab constants 23 | let value: String = module.get("MY_FAVOURITE_FOOD")?; 24 | assert_eq!(value, "saskatoonberries"); 25 | 26 | // We can call functions - the Undefined here just means we don't care 27 | // what type it returns 28 | module.call::("addBook", json_args!("My Favorite Martian"))?; 29 | module.call::( 30 | "addBook", 31 | json_args!("The Ultimate Saskatoon Berry Cookbook"), 32 | )?; 33 | 34 | // Functions can even be stored for later! 35 | // They can only be used on the runtime that made them, however 36 | let function: Function = module.get("listBooks")?; 37 | 38 | // The stored function can then be called! 39 | // Any serializable type can be retrieved as a function result or value 40 | let books: Vec = module.call_stored(&function, json_args!())?; 41 | assert_eq!( 42 | books, 43 | vec![ 44 | "My Favorite Martian", 45 | "The Ultimate Saskatoon Berry Cookbook" 46 | ] 47 | ); 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /examples/module_loader_cache.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! This example will demonstrate usage of the `ImportProvider` trait to implement a cache for module loading. 3 | //! This one will be a simple in-memory cache 4 | //! 5 | use deno_core::{error::ModuleLoaderError, ModuleSource, ModuleSpecifier}; 6 | use rustyscript::{module_loader::ImportProvider, Module, Runtime, RuntimeOptions}; 7 | use std::collections::HashMap; 8 | 9 | /// A simple in-memory cache for module loading 10 | #[derive(Default)] 11 | pub struct MemoryCache { 12 | cache: HashMap, 13 | } 14 | impl MemoryCache { 15 | /// Set a module in the cache 16 | pub fn set(&mut self, specifier: &str, source: String) { 17 | self.cache.insert(specifier.to_string(), source); 18 | } 19 | 20 | /// Get a module from the cache 21 | pub fn get(&self, specifier: &ModuleSpecifier) -> Option { 22 | self.cache.get(specifier.as_str()).cloned() 23 | } 24 | 25 | pub fn has(&self, specifier: &ModuleSpecifier) -> bool { 26 | self.cache.contains_key(specifier.as_str()) 27 | } 28 | } 29 | 30 | impl ImportProvider for MemoryCache { 31 | fn resolve( 32 | &mut self, 33 | specifier: &ModuleSpecifier, 34 | _referrer: &str, 35 | _kind: deno_core::ResolutionKind, 36 | ) -> Option> { 37 | // Tell the loader to allow the import if the module is in the cache 38 | self.get(specifier).map(|_| Ok(specifier.clone())) 39 | } 40 | 41 | fn import( 42 | &mut self, 43 | specifier: &ModuleSpecifier, 44 | _referrer: Option<&ModuleSpecifier>, 45 | _is_dyn_import: bool, 46 | _requested_module_type: deno_core::RequestedModuleType, 47 | ) -> Option> { 48 | // Return the source code if the module is in the cache 49 | self.get(specifier).map(Ok) 50 | } 51 | 52 | fn post_process( 53 | &mut self, 54 | specifier: &ModuleSpecifier, 55 | source: ModuleSource, 56 | ) -> Result { 57 | // Cache the source code 58 | if !self.has(specifier) { 59 | match &source.code { 60 | deno_core::ModuleSourceCode::String(s) => { 61 | self.set(specifier.as_str(), s.to_string()); 62 | } 63 | deno_core::ModuleSourceCode::Bytes(_) => {} 64 | } 65 | } 66 | Ok(source) 67 | } 68 | } 69 | 70 | fn main() -> Result<(), rustyscript::Error> { 71 | let mut cache = MemoryCache::default(); 72 | cache.set( 73 | "http://example.com/my_module.js", 74 | "export const foo = 'bar';".to_string(), 75 | ); 76 | 77 | let mut runtime = Runtime::new(RuntimeOptions { 78 | import_provider: Some(Box::new(cache)), 79 | ..Default::default() 80 | })?; 81 | 82 | let module = Module::new( 83 | "example.js", 84 | " 85 | import { foo } from 'http://example.com/my_module.js'; 86 | if (foo !== 'bar') { 87 | throw new Error('Expected foo to be bar'); 88 | } 89 | ", 90 | ); 91 | 92 | runtime.load_module(&module)?; 93 | Ok(()) 94 | } 95 | -------------------------------------------------------------------------------- /examples/multiple_modules.rs: -------------------------------------------------------------------------------- 1 | use rustyscript::{json_args, module, Error, Module, Runtime, RuntimeOptions, Undefined}; 2 | 3 | // This time we will embed this module into the executable directly. 4 | // After all, it is a very small file we will always need - why 5 | // take the extra overhead for the filesystem! 6 | const API_MODULE: Module = module!( 7 | "examples/get_value.ts", 8 | " 9 | let my_internal_value:number; 10 | 11 | export function getValue():number { 12 | return my_internal_value; 13 | } 14 | 15 | export function setValue(value:number) { 16 | my_internal_value = value * 2; 17 | } 18 | " 19 | ); 20 | 21 | fn main() -> Result<(), Error> { 22 | // First we need a runtime. There are a handful of options available 23 | // here but the one we need right now is default_entrypoint. 24 | // This tells the runtime that a function is needed for initial 25 | // setup of our runtime. 26 | let mut runtime = Runtime::new(RuntimeOptions { 27 | default_entrypoint: Some("setValue".to_string()), 28 | ..Default::default() 29 | })?; 30 | 31 | // Now we can include our static module - `to_module()` is needed 32 | // to pull into into a form we can use. 33 | // 34 | // The `call_entrypoint` function will call our module's setValue 35 | // function for us - the function was found and a reference to it 36 | // stored in advance on load so that this function call can be 37 | // made with less overhead. 38 | // Just like before, `::(&module_handle, json_args!(2))?; 42 | 43 | // Now we can load our new module from the filesystem 44 | // The handle that load_module returns is used to give context to future calls 45 | let use_value_handle = 46 | runtime.load_module(&Module::load("examples/javascript/multiple_modules.js")?)?; 47 | 48 | // We use the returned handle to extract the const that it exports! 49 | // We tell the compiler we'd like it as a string, and give the name of the value 50 | // We'd like to retrieve! 51 | let final_value: String = runtime.get_value(Some(&use_value_handle), "final_value")?; 52 | println!("The received value was {final_value}"); 53 | 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /examples/node_import/main.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example demonstrates importing and using node modules 3 | /// 4 | /// 2 node modules are imported in this example: 5 | /// - `os` from the Deno polyfills to the node standard library 6 | /// - `chalk` from npm, it will look for a matching package in the node_modules directory 7 | /// 8 | use rustyscript::{Error, Module, Runtime, RuntimeOptions}; 9 | 10 | fn main() { 11 | if let Err(e) = run() { 12 | eprintln!("Error: {}", e); 13 | } 14 | } 15 | 16 | fn run() -> Result<(), Error> { 17 | let module = Module::new( 18 | "test.js", 19 | r#" 20 | // From the node standard library (Deno polyfills) 21 | import os from "os"; 22 | 23 | // From npm 24 | import chalk from "npm:chalk@5"; 25 | 26 | export function print_hostname() { 27 | console.log("Getting hostname from node:os:"); 28 | console.log(chalk.blue(os.hostname())); 29 | } 30 | "#, 31 | ); 32 | 33 | // First we need a runtime, and to load the module we just created 34 | // We set the current directory to the examples/node_import directory 35 | // so that `node_modules` can be found 36 | let mut runtime = Runtime::new(RuntimeOptions::default())?; 37 | runtime.set_current_dir("examples/node_import")?; 38 | let module_handle = runtime.load_module(&module)?; 39 | 40 | // Now we can call the function we defined in the module 41 | // `::<()>` specifies that we don't expect any return value 42 | // This previously was deduced as `!` by the compiler, but that 43 | // feature is now being deprecated 44 | runtime.call_function::<()>(Some(&module_handle), "print_hostname", &())?; 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/.package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node_import", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "node_modules/chalk": { 8 | "version": "5.3.0", 9 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", 10 | "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", 11 | "engines": { 12 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 13 | }, 14 | "funding": { 15 | "url": "https://github.com/chalk/chalk?sponsor=1" 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/chalk/license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/chalk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chalk", 3 | "version": "5.3.0", 4 | "description": "Terminal string styling done right", 5 | "license": "MIT", 6 | "repository": "chalk/chalk", 7 | "funding": "https://github.com/chalk/chalk?sponsor=1", 8 | "type": "module", 9 | "main": "./source/index.js", 10 | "exports": "./source/index.js", 11 | "imports": { 12 | "#ansi-styles": "./source/vendor/ansi-styles/index.js", 13 | "#supports-color": { 14 | "node": "./source/vendor/supports-color/index.js", 15 | "default": "./source/vendor/supports-color/browser.js" 16 | } 17 | }, 18 | "types": "./source/index.d.ts", 19 | "engines": { 20 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 21 | }, 22 | "scripts": { 23 | "test": "xo && c8 ava && tsd", 24 | "bench": "matcha benchmark.js" 25 | }, 26 | "files": [ 27 | "source", 28 | "!source/index.test-d.ts" 29 | ], 30 | "keywords": [ 31 | "color", 32 | "colour", 33 | "colors", 34 | "terminal", 35 | "console", 36 | "cli", 37 | "string", 38 | "ansi", 39 | "style", 40 | "styles", 41 | "tty", 42 | "formatting", 43 | "rgb", 44 | "256", 45 | "shell", 46 | "xterm", 47 | "log", 48 | "logging", 49 | "command-line", 50 | "text" 51 | ], 52 | "devDependencies": { 53 | "@types/node": "^16.11.10", 54 | "ava": "^3.15.0", 55 | "c8": "^7.10.0", 56 | "color-convert": "^2.0.1", 57 | "execa": "^6.0.0", 58 | "log-update": "^5.0.0", 59 | "matcha": "^0.7.0", 60 | "tsd": "^0.19.0", 61 | "xo": "^0.53.0", 62 | "yoctodelay": "^2.0.0" 63 | }, 64 | "sideEffects": false, 65 | "xo": { 66 | "rules": { 67 | "unicorn/prefer-string-slice": "off", 68 | "@typescript-eslint/consistent-type-imports": "off", 69 | "@typescript-eslint/consistent-type-exports": "off", 70 | "@typescript-eslint/consistent-type-definitions": "off", 71 | "unicorn/expiring-todo-comments": "off" 72 | } 73 | }, 74 | "c8": { 75 | "reporter": [ 76 | "text", 77 | "lcov" 78 | ], 79 | "exclude": [ 80 | "source/vendor" 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/chalk/source/utilities.js: -------------------------------------------------------------------------------- 1 | // TODO: When targeting Node.js 16, use `String.prototype.replaceAll`. 2 | export function stringReplaceAll(string, substring, replacer) { 3 | let index = string.indexOf(substring); 4 | if (index === -1) { 5 | return string; 6 | } 7 | 8 | const substringLength = substring.length; 9 | let endIndex = 0; 10 | let returnValue = ''; 11 | do { 12 | returnValue += string.slice(endIndex, index) + substring + replacer; 13 | endIndex = index + substringLength; 14 | index = string.indexOf(substring, endIndex); 15 | } while (index !== -1); 16 | 17 | returnValue += string.slice(endIndex); 18 | return returnValue; 19 | } 20 | 21 | export function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) { 22 | let endIndex = 0; 23 | let returnValue = ''; 24 | do { 25 | const gotCR = string[index - 1] === '\r'; 26 | returnValue += string.slice(endIndex, (gotCR ? index - 1 : index)) + prefix + (gotCR ? '\r\n' : '\n') + postfix; 27 | endIndex = index + 1; 28 | index = string.indexOf('\n', endIndex); 29 | } while (index !== -1); 30 | 31 | returnValue += string.slice(endIndex); 32 | return returnValue; 33 | } 34 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/chalk/source/vendor/supports-color/browser.d.ts: -------------------------------------------------------------------------------- 1 | export {default} from './index.js'; 2 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/chalk/source/vendor/supports-color/browser.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | const level = (() => { 4 | if (navigator.userAgentData) { 5 | const brand = navigator.userAgentData.brands.find(({brand}) => brand === 'Chromium'); 6 | if (brand && brand.version > 93) { 7 | return 3; 8 | } 9 | } 10 | 11 | if (/\b(Chrome|Chromium)\//.test(navigator.userAgent)) { 12 | return 1; 13 | } 14 | 15 | return 0; 16 | })(); 17 | 18 | const colorSupport = level !== 0 && { 19 | level, 20 | hasBasic: true, 21 | has256: level >= 2, 22 | has16m: level >= 3, 23 | }; 24 | 25 | const supportsColor = { 26 | stdout: colorSupport, 27 | stderr: colorSupport, 28 | }; 29 | 30 | export default supportsColor; 31 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/chalk/source/vendor/supports-color/index.d.ts: -------------------------------------------------------------------------------- 1 | import type {WriteStream} from 'node:tty'; 2 | 3 | export type Options = { 4 | /** 5 | Whether `process.argv` should be sniffed for `--color` and `--no-color` flags. 6 | 7 | @default true 8 | */ 9 | readonly sniffFlags?: boolean; 10 | }; 11 | 12 | /** 13 | Levels: 14 | - `0` - All colors disabled. 15 | - `1` - Basic 16 colors support. 16 | - `2` - ANSI 256 colors support. 17 | - `3` - Truecolor 16 million colors support. 18 | */ 19 | export type ColorSupportLevel = 0 | 1 | 2 | 3; 20 | 21 | /** 22 | Detect whether the terminal supports color. 23 | */ 24 | export type ColorSupport = { 25 | /** 26 | The color level. 27 | */ 28 | level: ColorSupportLevel; 29 | 30 | /** 31 | Whether basic 16 colors are supported. 32 | */ 33 | hasBasic: boolean; 34 | 35 | /** 36 | Whether ANSI 256 colors are supported. 37 | */ 38 | has256: boolean; 39 | 40 | /** 41 | Whether Truecolor 16 million colors are supported. 42 | */ 43 | has16m: boolean; 44 | }; 45 | 46 | export type ColorInfo = ColorSupport | false; 47 | 48 | export function createSupportsColor(stream?: WriteStream, options?: Options): ColorInfo; 49 | 50 | declare const supportsColor: { 51 | stdout: ColorInfo; 52 | stderr: ColorInfo; 53 | }; 54 | 55 | export default supportsColor; 56 | -------------------------------------------------------------------------------- /examples/node_import/node_modules/chalk/source/vendor/supports-color/index.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | import os from 'node:os'; 3 | import tty from 'node:tty'; 4 | 5 | // From: https://github.com/sindresorhus/has-flag/blob/main/index.js 6 | /// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) { 7 | function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process.argv) { 8 | const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); 9 | const position = argv.indexOf(prefix + flag); 10 | const terminatorPosition = argv.indexOf('--'); 11 | return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); 12 | } 13 | 14 | const {env} = process; 15 | 16 | let flagForceColor; 17 | if ( 18 | hasFlag('no-color') 19 | || hasFlag('no-colors') 20 | || hasFlag('color=false') 21 | || hasFlag('color=never') 22 | ) { 23 | flagForceColor = 0; 24 | } else if ( 25 | hasFlag('color') 26 | || hasFlag('colors') 27 | || hasFlag('color=true') 28 | || hasFlag('color=always') 29 | ) { 30 | flagForceColor = 1; 31 | } 32 | 33 | function envForceColor() { 34 | if ('FORCE_COLOR' in env) { 35 | if (env.FORCE_COLOR === 'true') { 36 | return 1; 37 | } 38 | 39 | if (env.FORCE_COLOR === 'false') { 40 | return 0; 41 | } 42 | 43 | return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); 44 | } 45 | } 46 | 47 | function translateLevel(level) { 48 | if (level === 0) { 49 | return false; 50 | } 51 | 52 | return { 53 | level, 54 | hasBasic: true, 55 | has256: level >= 2, 56 | has16m: level >= 3, 57 | }; 58 | } 59 | 60 | function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) { 61 | const noFlagForceColor = envForceColor(); 62 | if (noFlagForceColor !== undefined) { 63 | flagForceColor = noFlagForceColor; 64 | } 65 | 66 | const forceColor = sniffFlags ? flagForceColor : noFlagForceColor; 67 | 68 | if (forceColor === 0) { 69 | return 0; 70 | } 71 | 72 | if (sniffFlags) { 73 | if (hasFlag('color=16m') 74 | || hasFlag('color=full') 75 | || hasFlag('color=truecolor')) { 76 | return 3; 77 | } 78 | 79 | if (hasFlag('color=256')) { 80 | return 2; 81 | } 82 | } 83 | 84 | // Check for Azure DevOps pipelines. 85 | // Has to be above the `!streamIsTTY` check. 86 | if ('TF_BUILD' in env && 'AGENT_NAME' in env) { 87 | return 1; 88 | } 89 | 90 | if (haveStream && !streamIsTTY && forceColor === undefined) { 91 | return 0; 92 | } 93 | 94 | const min = forceColor || 0; 95 | 96 | if (env.TERM === 'dumb') { 97 | return min; 98 | } 99 | 100 | if (process.platform === 'win32') { 101 | // Windows 10 build 10586 is the first Windows release that supports 256 colors. 102 | // Windows 10 build 14931 is the first release that supports 16m/TrueColor. 103 | const osRelease = os.release().split('.'); 104 | if ( 105 | Number(osRelease[0]) >= 10 106 | && Number(osRelease[2]) >= 10_586 107 | ) { 108 | return Number(osRelease[2]) >= 14_931 ? 3 : 2; 109 | } 110 | 111 | return 1; 112 | } 113 | 114 | if ('CI' in env) { 115 | if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) { 116 | return 3; 117 | } 118 | 119 | if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') { 120 | return 1; 121 | } 122 | 123 | return min; 124 | } 125 | 126 | if ('TEAMCITY_VERSION' in env) { 127 | return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; 128 | } 129 | 130 | if (env.COLORTERM === 'truecolor') { 131 | return 3; 132 | } 133 | 134 | if (env.TERM === 'xterm-kitty') { 135 | return 3; 136 | } 137 | 138 | if ('TERM_PROGRAM' in env) { 139 | const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); 140 | 141 | switch (env.TERM_PROGRAM) { 142 | case 'iTerm.app': { 143 | return version >= 3 ? 3 : 2; 144 | } 145 | 146 | case 'Apple_Terminal': { 147 | return 2; 148 | } 149 | // No default 150 | } 151 | } 152 | 153 | if (/-256(color)?$/i.test(env.TERM)) { 154 | return 2; 155 | } 156 | 157 | if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { 158 | return 1; 159 | } 160 | 161 | if ('COLORTERM' in env) { 162 | return 1; 163 | } 164 | 165 | return min; 166 | } 167 | 168 | export function createSupportsColor(stream, options = {}) { 169 | const level = _supportsColor(stream, { 170 | streamIsTTY: stream && stream.isTTY, 171 | ...options, 172 | }); 173 | 174 | return translateLevel(level); 175 | } 176 | 177 | const supportsColor = { 178 | stdout: createSupportsColor({isTTY: tty.isatty(1)}), 179 | stderr: createSupportsColor({isTTY: tty.isatty(2)}), 180 | }; 181 | 182 | export default supportsColor; 183 | -------------------------------------------------------------------------------- /examples/node_import/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node_import", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "ISC", 8 | "dependencies": { 9 | "chalk": "^5.3.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/runtime_extensions.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows how to add deno_core extensions into the runtime. 3 | /// 4 | /// Extensions like the one being used (see examples/ext/example_extension.rs) 5 | /// allow you to call rust code from within JS 6 | /// 7 | /// Extensions consist of a set of #[op2] functions, an extension! macro, 8 | /// and one or more optional JS modules. 9 | /// 10 | use rustyscript::{Error, Module, Runtime, RuntimeOptions}; 11 | use std::collections::HashSet; 12 | 13 | mod example_extension; 14 | 15 | fn main() -> Result<(), Error> { 16 | let module = Module::new( 17 | "test.js", 18 | r#" 19 | import { add } from "example:calculator"; 20 | export const result = add(5, 5); 21 | "#, 22 | ); 23 | 24 | // Whitelist the example: schema for the module 25 | let mut schema_whlist = HashSet::new(); 26 | schema_whlist.insert("example:".to_string()); 27 | 28 | // We provide a function returning the set of extensions to load 29 | // It needs to be a function, since deno_core does not currently 30 | // allow clone or copy from extensions 31 | let mut runtime = Runtime::new(RuntimeOptions { 32 | schema_whlist, 33 | extensions: vec![example_extension::example_extension::init_ops_and_esm()], 34 | ..Default::default() 35 | })?; 36 | let module_handle = runtime.load_module(&module)?; 37 | 38 | let result: i64 = runtime.get_value(Some(&module_handle), "result")?; 39 | assert_eq!(10, result); 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /examples/serialized_types.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example is meant to demonstrate sending and receiving custom types 3 | /// between JS and rust 4 | /// 5 | use rustyscript::{module, Error, Module, ModuleWrapper}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | // Modules can be defined statically using this macro! 9 | static MY_MODULE: Module = module!( 10 | "custom_types.js", 11 | " 12 | // Mapping the enum types over like this isn't strictly needed 13 | // But it does help prevent bugs! 14 | const BridgeCrossingResults = { 15 | Permitted: 'Permitted', 16 | Denied: 'Denied' 17 | }; 18 | 19 | const Quests = { 20 | HolyGrail: 'HolyGrail', 21 | Groceries: 'Groceries', 22 | Sandwich: 'Sandwich', 23 | } 24 | 25 | export function checkAttempt(attempt) { 26 | if (attempt.quest == Quests.HolyGrail) { 27 | return BridgeCrossingResults.Permitted; 28 | } else { 29 | return BridgeCrossingResults.Denied; 30 | } 31 | } 32 | " 33 | ); 34 | 35 | /// This enum will be used by both rust and JS 36 | /// It will be returned by JS, and thus needs Deserialize 37 | /// The other 2 traits are only for the assert_eq! macro below 38 | #[derive(Deserialize, PartialEq, Debug)] 39 | enum BridgeCrossingResult { 40 | Permitted, 41 | Denied, 42 | } 43 | 44 | /// This enum will be used by both rust and JS 45 | /// Since it is being send to JS, it needs Serialize 46 | #[derive(Serialize)] 47 | enum Quest { 48 | HolyGrail, 49 | Groceries, 50 | } 51 | 52 | /// This type will be sent into the JS module 53 | /// Since it is being send to JS, it needs Serialize 54 | #[derive(Serialize)] 55 | struct BridgeCrossingAttempt { 56 | name: String, 57 | quest: Quest, 58 | favourite_colour: String, 59 | } 60 | 61 | fn main() -> Result<(), Error> { 62 | // We only have one source file, so it is simpler here to just use this wrapper type 63 | // As opposed to building a complete runtime. 64 | let mut module = ModuleWrapper::new_from_module(&MY_MODULE, Default::default())?; 65 | 66 | // Although we can use json_args!() to call a function with primitives as arguments 67 | // More complicated types must use `Runtime::arg` 68 | let result: BridgeCrossingResult = module.call( 69 | "checkAttempt", 70 | &BridgeCrossingAttempt { 71 | name: "Lancelot".to_string(), 72 | quest: Quest::Groceries, 73 | favourite_colour: "blue".to_string(), 74 | }, 75 | )?; 76 | assert_eq!(result, BridgeCrossingResult::Denied); 77 | 78 | // Let us try again with different values... 79 | let result: BridgeCrossingResult = module.call( 80 | "checkAttempt", 81 | &BridgeCrossingAttempt { 82 | name: "Lancelot".to_string(), 83 | quest: Quest::HolyGrail, 84 | favourite_colour: "blue".to_string(), 85 | }, 86 | )?; 87 | assert_eq!(result, BridgeCrossingResult::Permitted); 88 | 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /examples/thread_safety.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// rustyscript is not thread-safe 3 | /// This is due to a limitation of the underlying engine, deno_core 4 | /// However, rustyscript provides a mechanism to safely use it in a static context 5 | /// 6 | /// See `examples/default_threaded_worker` and `examples/custom_threaded_worker` 7 | /// for a more flexible way to run rustyscript in a threaded environment 8 | /// 9 | use rustyscript::{module, static_runtime, Error, Module, RuntimeOptions}; 10 | use std::time::Duration; 11 | 12 | static_runtime!(RUNTIME, { 13 | RuntimeOptions { 14 | timeout: Duration::from_secs(5), 15 | ..Default::default() 16 | } 17 | }); 18 | 19 | // Modules can be defined statically using this macro! 20 | static MY_MODULE: Module = module!( 21 | "custom_types.js", 22 | " 23 | export const my_function = () => 'test'; 24 | " 25 | ); 26 | 27 | fn main() -> Result<(), Error> { 28 | let value: String = RUNTIME::with(|runtime| { 29 | let module_context = runtime.load_module(&MY_MODULE)?; 30 | runtime.call_function(Some(&module_context), "my_function", &()) 31 | })?; 32 | 33 | assert_eq!(value, "test"); 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/typescript_modules.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows a basic usage of the runtime 3 | /// with Typescript modules 4 | /// 5 | /// They will get transpiled automatically into JS, but 6 | /// provide no type checking at this time 7 | /// 8 | /// The call to Runtime::execute_module is a one liner which: 9 | /// - Creates a new runtime with the given options 10 | /// - Loads the modules passed in 11 | /// - Calls the module's entrypoint function 12 | /// - Either a default passed as an option 13 | /// - Or the module's default export 14 | /// - Or a call in JS to rustyscript.register_entrypoint 15 | /// - Returns the result 16 | /// 17 | /// Instead of just vec![], one could pass in other JS modules 18 | /// which could be imported using `import './filename.js';` 19 | /// 20 | use rustyscript::{json_args, Error, Module, Runtime}; 21 | 22 | fn main() -> Result<(), Error> { 23 | let module = Module::new( 24 | "test.ts", 25 | " 26 | export default (string: string, integer: number): number => { 27 | console.log(`Hello world: string=${string}, integer=${integer}`); 28 | return 2; 29 | } 30 | ", 31 | ); 32 | 33 | // Execute the module above as an ES module 34 | // Do not side-load any additional modules 35 | // Use the default Runtime options 36 | // Pass 2 args into the entrypoint function 37 | // And expect a usize back from it 38 | let value: usize = 39 | Runtime::execute_module(&module, vec![], Default::default(), json_args!("test", 5))?; 40 | 41 | assert_eq!(value, 2); 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /examples/url_import.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example shows a basic usage of the runtime 3 | /// 4 | /// The call to Runtime::execute_module is a one liner which: 5 | /// - Creates a new runtime with the given options 6 | /// - Loads the modules passed in 7 | /// - Calls the module's entrypoint function 8 | /// - Either a default passed as an option 9 | /// - Or the module's default export 10 | /// - Or a call in JS to rustyscript.register_entrypoint 11 | /// - Returns the result 12 | /// 13 | /// Instead of just vec![], one could pass in other JS modules 14 | /// which could be imported using `import './filename.js';` 15 | /// 16 | use rustyscript::{Error, Module, Runtime}; 17 | 18 | fn main() -> Result<(), Error> { 19 | let module = Module::new( 20 | "test.js", 21 | " 22 | // Importing a module first loaded by rust 23 | import * as test2 from './test2.js'; 24 | 25 | // Importing a module from the filesystem - requires 'web' or 'fs_import' crate feature 26 | import * as fsimport from './examples/javascript/example_module.js'; 27 | 28 | // Importing a module from the web - requires 'web' or 'url_import' crate feature 29 | import * as json from 'https://deno.land/std@0.206.0/json/common.ts'; 30 | ", 31 | ); 32 | let module2 = Module::new("test2.js", ""); 33 | 34 | let mut runtime = Runtime::new(Default::default())?; 35 | runtime.load_module(&module2)?; 36 | runtime.load_module(&module)?; 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/web_features.rs: -------------------------------------------------------------------------------- 1 | use rustyscript::{json_args, Error, Module, Runtime, RuntimeOptions}; 2 | /// 3 | /// This example shows features requiring the 'web' feature to work 4 | /// Stuff like setTimeout, atob/btoa, file reads and fetch are all examples 5 | /// 6 | /// We will focus on timers and fetch here 7 | /// 8 | use std::time::Duration; 9 | 10 | fn main() -> Result<(), Error> { 11 | // This module has an async function, which is not itself a problem 12 | // However, it uses setTimeout - the timer will never be triggered 13 | // unless the web feature is active. 14 | // See above for a longer list for web feature exclusives 15 | let module = Module::new( 16 | "test.js", 17 | " 18 | const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); 19 | export async function test() { 20 | await sleep(10); 21 | return 2; 22 | } 23 | 24 | export async function fetch_example() { 25 | return new Promise((accept, reject) => { 26 | fetch('https://api.github.com/users/mralexgray/repos', { 27 | method: 'GET', 28 | headers: { 29 | Accept: 'application/json', 30 | }, 31 | }).then(response => response.json()) 32 | .then(json => accept(json)) 33 | .catch(e => reject(e)); 34 | }); 35 | } 36 | 37 | export async function event_source_example() { 38 | return new Promise((accept, reject) => { 39 | var source = new EventSource('https://www.w3schools.com/html/demo_sse.php'); 40 | source.addEventListener('message', (e) => { 41 | accept(e.data); 42 | }); 43 | source.addEventListener('error', (e) => { 44 | reject(e) 45 | }); 46 | }); 47 | } 48 | ", 49 | ); 50 | 51 | // We add a timeout to the runtime anytime async might be used 52 | let mut runtime = Runtime::new(RuntimeOptions { 53 | timeout: Duration::from_millis(10000), 54 | ..Default::default() 55 | })?; 56 | 57 | // The async function 58 | let module_handle = runtime.load_module(&module)?; 59 | let value: usize = runtime.call_function(Some(&module_handle), "test", json_args!())?; 60 | println!("Got value: {}", value); 61 | assert_eq!(value, 2); 62 | 63 | // Fetch example 64 | let data: rustyscript::serde_json::Value = 65 | runtime.call_function(Some(&module_handle), "fetch_example", json_args!())?; 66 | println!("Got {:?} bytes", data.to_string().len()); 67 | 68 | // EventSource example 69 | let data: rustyscript::serde_json::Value = 70 | runtime.call_function(Some(&module_handle), "event_source_example", json_args!())?; 71 | println!("Got event: {}", data); 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /examples/websocket.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example demonstrates a use for the websockets extension. 3 | /// It will open a connection to the echo server at wss://echo.websocket.org 4 | /// Send a message 'ping', wait for a response, and then close the connection. 5 | /// 6 | use rustyscript::{json_args, Error, Module, Runtime, RuntimeOptions, Undefined}; 7 | 8 | fn main() -> Result<(), Error> { 9 | let module = Module::new( 10 | "test.js", 11 | " 12 | export async function connect(url) { 13 | return new Promise((resolve, reject) => { 14 | const ws = new WebSocket(url); 15 | 16 | ws.addEventListener('open', () => { 17 | console.log('WebSocket connection opened'); 18 | console.log(`Sending message 'ping'`); 19 | ws.send('ping'); 20 | }); 21 | 22 | ws.addEventListener('message', (event) => { 23 | console.log(`Received '${event.data}'`); 24 | ws.close(); 25 | }); 26 | 27 | ws.addEventListener('close', (event) => { 28 | if (event.wasClean) { 29 | console.log(`Connection closed, code=${event.code} reason='${event.reason}'`); 30 | resolve(); 31 | } else { 32 | console.log('Connection died'); 33 | reject(new Error('Connection died')); 34 | } 35 | }); 36 | 37 | ws.addEventListener('error', (e) => { 38 | console.log(`Error: ${e}`); 39 | reject(new Error(`Error: ${e}`)); 40 | }); 41 | }); 42 | } 43 | ", 44 | ); 45 | 46 | let mut runtime = Runtime::new(RuntimeOptions { 47 | default_entrypoint: Some("connect".to_string()), 48 | ..Default::default() 49 | })?; 50 | 51 | let module_handle = runtime.load_module(&module)?; 52 | 53 | runtime.call_entrypoint::(&module_handle, json_args!("wss://echo.websocket.org"))?; 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /examples/worker_pool.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// This example demonstrates how to use the worker pool to run multiple workers in parallel. 3 | /// 4 | use rustyscript::{ 5 | worker::{DefaultWorker, DefaultWorkerQuery, WorkerPool}, 6 | Error, Module, 7 | }; 8 | 9 | fn main() -> Result<(), Error> { 10 | // We first will create a worker pool with 4 workers 11 | let mut pool = WorkerPool::::new(Default::default(), 4)?; 12 | 13 | // We will now create a module that will perform a long running operation 14 | // We will run this in parallel on two workers to demonstrate the worker pool 15 | let module = Module::new( 16 | "test.js", 17 | " 18 | // Perform a long running operation 19 | for (let i = 0; i < 10000000000; i++) { 20 | // Do nothing 21 | } 22 | ", 23 | ); 24 | 25 | // 26 | // Start the operation on the first worker 27 | println!("Start load on A..."); 28 | let worker_a = pool.next_worker(); 29 | let query = DefaultWorkerQuery::LoadModule(module.clone()); 30 | worker_a.borrow().send(query)?; // We don't need to wait for the response right away! 31 | 32 | // 33 | // Start the operation on the second worker 34 | println!("Start load on B..."); 35 | let worker_b = pool.next_worker(); 36 | let query = DefaultWorkerQuery::LoadModule(module.clone()); 37 | worker_b.borrow().send(query)?; // We don't need to wait for the response here either 38 | 39 | // 40 | // We can now wait for the responses 41 | print!("Waiting for the workers to finish... "); 42 | worker_a.borrow().receive()?; 43 | print!("Done A... "); 44 | worker_b.borrow().receive()?; 45 | println!("Done B!"); 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /src/async_bridge.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use std::rc::Rc; 3 | use tokio_util::sync::CancellationToken; 4 | 5 | /// A wrapper around the tokio runtime allowing for borrowed usage 6 | /// 7 | /// The borrowed variant is useful for use with `tokio::main`. 8 | #[derive(Clone)] 9 | pub enum TokioRuntime { 10 | /// The runtime is borrowed and will not be dropped when this is dropped 11 | Borrowed(tokio::runtime::Handle), 12 | 13 | /// The runtime is owned and will be dropped when this is dropped 14 | Owned(Rc), 15 | } 16 | impl TokioRuntime { 17 | /// Returns a borrowed handle to the runtime 18 | #[must_use] 19 | pub fn handle(&self) -> tokio::runtime::Handle { 20 | match self { 21 | Self::Borrowed(handle) => handle.clone(), 22 | Self::Owned(rt) => rt.handle().clone(), 23 | } 24 | } 25 | 26 | /// Runs a future to completion on this Handle's associated Runtime. 27 | /// 28 | /// 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. 29 | /// 30 | /// 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. 31 | /// 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. 32 | pub fn block_on(&self, f: F) -> T 33 | where 34 | F: std::future::Future, 35 | { 36 | match self { 37 | Self::Borrowed(handle) => handle.block_on(f), 38 | Self::Owned(rt) => rt.block_on(f), 39 | } 40 | } 41 | } 42 | 43 | /// A bridge to the tokio runtime that connects the Deno and Tokio runtimes 44 | /// Implements common patterns used throughout the codebase 45 | pub struct AsyncBridge { 46 | tokio: TokioRuntime, 47 | timeout: std::time::Duration, 48 | heap_exhausted_token: CancellationToken, 49 | } 50 | 51 | impl AsyncBridge { 52 | /// Creates a new instance with the provided options. 53 | /// A new tokio runtime will be created with the provided timeout. 54 | pub fn new(timeout: std::time::Duration) -> Result { 55 | let tokio = Rc::new( 56 | tokio::runtime::Builder::new_current_thread() 57 | .enable_all() 58 | .thread_keep_alive(timeout) 59 | .build()?, 60 | ); 61 | 62 | Ok(Self::with_tokio_runtime(timeout, tokio)) 63 | } 64 | 65 | /// Creates a new instance with the provided options and a pre-configured tokio runtime. 66 | pub fn with_tokio_runtime( 67 | timeout: std::time::Duration, 68 | tokio: Rc, 69 | ) -> Self { 70 | let heap_exhausted_token = CancellationToken::new(); 71 | let tokio = TokioRuntime::Owned(tokio); 72 | Self { 73 | tokio, 74 | timeout, 75 | heap_exhausted_token, 76 | } 77 | } 78 | 79 | /// Creates a new instance with the provided options and a borrowed tokio runtime handle. 80 | pub fn with_runtime_handle( 81 | timeout: std::time::Duration, 82 | handle: tokio::runtime::Handle, 83 | ) -> Self { 84 | let heap_exhausted_token = CancellationToken::new(); 85 | let tokio = TokioRuntime::Borrowed(handle); 86 | Self { 87 | tokio, 88 | timeout, 89 | heap_exhausted_token, 90 | } 91 | } 92 | 93 | /// Access the underlying tokio runtime used for blocking operations 94 | #[must_use] 95 | pub fn tokio_runtime(&self) -> TokioRuntime { 96 | self.tokio.clone() 97 | } 98 | 99 | /// Destroy instance, releasing all resources 100 | /// Then the internal tokio runtime will be returned 101 | #[must_use] 102 | pub fn into_tokio_runtime(self) -> TokioRuntime { 103 | self.tokio 104 | } 105 | 106 | /// Returns the timeout for the runtime 107 | #[must_use] 108 | pub fn timeout(&self) -> std::time::Duration { 109 | self.timeout 110 | } 111 | 112 | /// Returns the heap exhausted token for the runtime 113 | /// Used to detect when the runtime has run out of memory 114 | #[must_use] 115 | pub fn heap_exhausted_token(&self) -> CancellationToken { 116 | self.heap_exhausted_token.clone() 117 | } 118 | } 119 | 120 | pub trait AsyncBridgeExt { 121 | fn bridge(&self) -> &AsyncBridge; 122 | 123 | fn block_on<'a, Out, F, Fut>(&'a mut self, f: F) -> Result 124 | where 125 | Fut: std::future::Future>, 126 | F: FnOnce(&'a mut Self) -> Fut, 127 | { 128 | let timeout = self.bridge().timeout(); 129 | let rt = self.bridge().tokio_runtime(); 130 | let heap_exhausted_token = self.bridge().heap_exhausted_token(); 131 | 132 | rt.block_on(async move { 133 | tokio::select! { 134 | result = tokio::time::timeout(timeout, f(self)) => result?, 135 | () = heap_exhausted_token.cancelled() => Err(Error::HeapExhausted), 136 | } 137 | }) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/ext/broadcast_channel/init_broadcast_channel.js: -------------------------------------------------------------------------------- 1 | import * as broadcastChannel from "ext:deno_broadcast_channel/01_broadcast_channel.js"; 2 | import { core } from "ext:core/mod.js"; 3 | 4 | function broadcast_serialize(data) { 5 | let uint8Array = core.serialize(data); 6 | return Array.from(uint8Array); 7 | } 8 | 9 | function broadcast_deserialize(data, data2) { 10 | let uint8Array = Uint8Array.from(data); 11 | return core.deserialize(uint8Array); 12 | } 13 | 14 | import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 15 | applyToGlobal({ 16 | BroadcastChannel: nonEnumerable(broadcastChannel.BroadcastChannel), 17 | broadcast_serialize: nonEnumerable(broadcast_serialize), 18 | broadcast_deserialize: nonEnumerable(broadcast_deserialize), 19 | }); 20 | -------------------------------------------------------------------------------- /src/ext/broadcast_channel/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_broadcast_channel::InMemoryBroadcastChannel; 3 | use deno_core::{extension, Extension}; 4 | 5 | mod wrapper; 6 | pub use wrapper::BroadcastChannelWrapper; 7 | 8 | extension!( 9 | init_broadcast_channel, 10 | deps = [rustyscript], 11 | esm_entry_point = "ext:init_broadcast_channel/init_broadcast_channel.js", 12 | esm = [ dir "src/ext/broadcast_channel", "init_broadcast_channel.js" ], 13 | ); 14 | impl ExtensionTrait<()> for init_broadcast_channel { 15 | fn init((): ()) -> Extension { 16 | init_broadcast_channel::init_ops_and_esm() 17 | } 18 | } 19 | impl ExtensionTrait for deno_broadcast_channel::deno_broadcast_channel { 20 | fn init(channel: InMemoryBroadcastChannel) -> Extension { 21 | deno_broadcast_channel::deno_broadcast_channel::init_ops_and_esm(channel) 22 | } 23 | } 24 | 25 | pub fn extensions(channel: InMemoryBroadcastChannel, is_snapshot: bool) -> Vec { 26 | vec![ 27 | deno_broadcast_channel::deno_broadcast_channel::build(channel, is_snapshot), 28 | init_broadcast_channel::build((), is_snapshot), 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/ext/broadcast_channel/wrapper.rs: -------------------------------------------------------------------------------- 1 | use crate::{big_json_args, Error, Runtime}; 2 | use deno_broadcast_channel::BroadcastChannel; 3 | use serde::{de::DeserializeOwned, Serialize}; 4 | use std::time::Duration; 5 | 6 | /// Helper struct to wrap a broadcast channel 7 | /// Takes care of some of the boilerplate for serialization/deserialization 8 | pub struct BroadcastChannelWrapper { 9 | channel: Channel, 10 | resource: ::Resource, 11 | name: String, 12 | } 13 | impl BroadcastChannelWrapper { 14 | /// Create a new broadcast channel wrapper and subscribe to the channel 15 | /// Unsubscribe is called when the wrapper is dropped 16 | /// 17 | /// # Errors 18 | /// Will return an error if the channel cannot be subscribed to 19 | pub fn new(channel: &Channel, name: impl ToString) -> Result { 20 | let channel = channel.clone(); 21 | let resource = channel.subscribe()?; 22 | let name = name.to_string(); 23 | Ok(Self { 24 | channel, 25 | resource, 26 | name, 27 | }) 28 | } 29 | 30 | /// Send a message to the channel, blocking until the message is sent 31 | /// 32 | /// # Errors 33 | /// Will return an error if the message cannot be serialized or sent 34 | pub fn send_sync(&self, runtime: &mut Runtime, data: T) -> Result<(), Error> { 35 | let tokio_rt = runtime.tokio_runtime(); 36 | tokio_rt.block_on(self.send(runtime, data)) 37 | } 38 | 39 | /// Send a message to the channel 40 | /// 41 | /// # Errors 42 | /// Will return an error if the message cannot be serialized or sent 43 | pub async fn send(&self, runtime: &mut Runtime, data: T) -> Result<(), Error> { 44 | let data: Vec = runtime 45 | .call_function_async(None, "broadcast_serialize", &data) 46 | .await?; 47 | self.channel 48 | .send(&self.resource, self.name.clone(), data) 49 | .await?; 50 | Ok(()) 51 | } 52 | 53 | /// Receive a message from the channel, waiting for a message to arrive, or until the timeout is reached 54 | /// 55 | /// # Errors 56 | /// Will return an error if the message cannot be deserialized 57 | /// or if receiving the message fails 58 | pub async fn recv( 59 | &self, 60 | runtime: &mut Runtime, 61 | timeout: Option, 62 | ) -> Result, Error> { 63 | let msg = if let Some(timeout) = timeout { 64 | tokio::select! { 65 | msg = self.channel.recv(&self.resource) => msg, 66 | () = tokio::time::sleep(timeout) => Ok(None), 67 | } 68 | } else { 69 | self.channel.recv(&self.resource).await 70 | }?; 71 | 72 | let Some((name, data)) = msg else { 73 | return Ok(None); 74 | }; 75 | 76 | if name == self.name { 77 | let data: T = runtime 78 | .call_function_async(None, "broadcast_deserialize", big_json_args!(data)) 79 | .await?; 80 | Ok(Some(data)) 81 | } else { 82 | Ok(None) 83 | } 84 | } 85 | 86 | /// Receive a message from the channel, blocking until a message arrives, or until the timeout is reached 87 | /// 88 | /// # Errors 89 | /// Will return an error if the message cannot be deserialized 90 | /// or if receiving the message fails 91 | pub fn recv_sync( 92 | &self, 93 | runtime: &mut Runtime, 94 | timeout: Option, 95 | ) -> Result, Error> { 96 | let tokio_rt = runtime.tokio_runtime(); 97 | tokio_rt.block_on(self.recv(runtime, timeout)) 98 | } 99 | } 100 | 101 | impl Drop for BroadcastChannelWrapper { 102 | fn drop(&mut self) { 103 | self.channel.unsubscribe(&self.resource).ok(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/ext/cache/init_cache.js: -------------------------------------------------------------------------------- 1 | import * as caches from "ext:deno_cache/01_cache.js"; 2 | 3 | import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 4 | applyToGlobal({ 5 | caches: { 6 | enumerable: true, 7 | configurable: true, 8 | get: caches.cacheStorage, 9 | }, 10 | CacheStorage: nonEnumerable(caches.CacheStorage), 11 | Cache: nonEnumerable(caches.Cache), 12 | }); -------------------------------------------------------------------------------- /src/ext/cache/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | //mod cache_backend; 5 | //mod memory; 6 | //pub use cache_backend::CacheBackend; 7 | 8 | extension!( 9 | init_cache, 10 | deps = [rustyscript], 11 | esm_entry_point = "ext:init_cache/init_cache.js", 12 | esm = [ dir "src/ext/cache", "init_cache.js" ], 13 | ); 14 | impl ExtensionTrait<()> for init_cache { 15 | fn init((): ()) -> Extension { 16 | init_cache::init_ops_and_esm() 17 | } 18 | } 19 | impl ExtensionTrait> for deno_cache::deno_cache { 20 | fn init(options: Option) -> Extension { 21 | deno_cache::deno_cache::init_ops_and_esm(options) 22 | } 23 | } 24 | 25 | pub fn extensions(options: Option, is_snapshot: bool) -> Vec { 26 | vec![ 27 | deno_cache::deno_cache::build(options, is_snapshot), 28 | init_cache::build((), is_snapshot), 29 | ] 30 | } 31 | 32 | /* 33 | Custom caches were removed by deno 34 | 35 | #[cfg(test)] 36 | mod test { 37 | use crate::{Module, Runtime, RuntimeOptions}; 38 | 39 | #[test] 40 | fn test_default_mem_cache() { 41 | let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); 42 | let module = Module::new( 43 | "test.js", 44 | " 45 | let cache = await caches.open('my_cache'); 46 | 47 | fetch('http://web.simmons.edu/').then((response) => { 48 | cache.put('http://web.simmons.edu/', response); 49 | }); 50 | 51 | cache.match('http://web.simmons.edu/').then((response) => { 52 | console.log('Got response from cache!'); 53 | }); 54 | ", 55 | ); 56 | 57 | runtime.load_module(&module).unwrap(); 58 | } 59 | } 60 | */ 61 | -------------------------------------------------------------------------------- /src/ext/console/init_console.js: -------------------------------------------------------------------------------- 1 | import * as _console from 'ext:deno_console/01_console.js'; 2 | 3 | import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 4 | applyToGlobal({ 5 | console: nonEnumerable( 6 | new _console.Console((msg, level) => globalThis.Deno.core.print(msg, level > 1)), 7 | ), 8 | }); 9 | 10 | globalThis.Deno.inspect = _console.inspect; -------------------------------------------------------------------------------- /src/ext/console/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | extension!( 5 | init_console, 6 | deps = [rustyscript], 7 | esm_entry_point = "ext:init_console/init_console.js", 8 | esm = [ dir "src/ext/console", "init_console.js" ], 9 | ); 10 | impl ExtensionTrait<()> for init_console { 11 | fn init((): ()) -> Extension { 12 | deno_terminal::colors::set_use_color(true); 13 | init_console::init_ops_and_esm() 14 | } 15 | } 16 | impl ExtensionTrait<()> for deno_console::deno_console { 17 | fn init((): ()) -> Extension { 18 | deno_console::deno_console::init_ops_and_esm() 19 | } 20 | } 21 | 22 | pub fn extensions(is_snapshot: bool) -> Vec { 23 | vec![ 24 | deno_console::deno_console::build((), is_snapshot), 25 | init_console::build((), is_snapshot), 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/ext/cron/init_cron.js: -------------------------------------------------------------------------------- 1 | import * as cron from 'ext:deno_cron/01_cron.ts'; 2 | globalThis.Deno.cron = cron.cron; -------------------------------------------------------------------------------- /src/ext/cron/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | use deno_cron::local::LocalCronHandler; 4 | 5 | extension!( 6 | init_cron, 7 | deps = [rustyscript], 8 | esm_entry_point = "ext:init_cron/init_cron.js", 9 | esm = [ dir "src/ext/cron", "init_cron.js" ], 10 | ); 11 | impl ExtensionTrait<()> for init_cron { 12 | fn init((): ()) -> Extension { 13 | init_cron::init_ops_and_esm() 14 | } 15 | } 16 | impl ExtensionTrait<()> for deno_cron::deno_cron { 17 | fn init((): ()) -> Extension { 18 | deno_cron::deno_cron::init_ops_and_esm(LocalCronHandler::new()) 19 | } 20 | } 21 | 22 | pub fn extensions(is_snapshot: bool) -> Vec { 23 | vec![ 24 | deno_cron::deno_cron::build((), is_snapshot), 25 | init_cron::build((), is_snapshot), 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/ext/crypto/init_crypto.js: -------------------------------------------------------------------------------- 1 | import * as crypto from "ext:deno_crypto/00_crypto.js"; 2 | 3 | import { applyToGlobal, nonEnumerable, readOnly } from 'ext:rustyscript/rustyscript.js'; 4 | applyToGlobal({ 5 | CryptoKey: nonEnumerable(crypto.CryptoKey), 6 | crypto: readOnly(crypto.crypto), 7 | Crypto: nonEnumerable(crypto.Crypto), 8 | SubtleCrypto: nonEnumerable(crypto.SubtleCrypto), 9 | }); 10 | -------------------------------------------------------------------------------- /src/ext/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | extension!( 5 | init_crypto, 6 | deps = [rustyscript], 7 | esm_entry_point = "ext:init_crypto/init_crypto.js", 8 | esm = [ dir "src/ext/crypto", "init_crypto.js" ], 9 | ); 10 | impl ExtensionTrait<()> for init_crypto { 11 | fn init((): ()) -> Extension { 12 | init_crypto::init_ops_and_esm() 13 | } 14 | } 15 | impl ExtensionTrait> for deno_crypto::deno_crypto { 16 | fn init(seed: Option) -> Extension { 17 | deno_crypto::deno_crypto::init_ops_and_esm(seed) 18 | } 19 | } 20 | 21 | pub fn extensions(seed: Option, is_snapshot: bool) -> Vec { 22 | vec![ 23 | deno_crypto::deno_crypto::build(seed, is_snapshot), 24 | init_crypto::build((), is_snapshot), 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/ext/ffi/init_ffi.js: -------------------------------------------------------------------------------- 1 | import * as ffi from 'ext:deno_ffi/00_ffi.js'; 2 | 3 | globalThis.Deno.dlopen = ffi.dlopen; 4 | globalThis.Deno.UnsafeCallback = ffi.UnsafeCallback; 5 | globalThis.Deno.UnsafePointer = ffi.UnsafePointer; 6 | globalThis.Deno.UnsafePointerView = ffi.UnsafePointerView; 7 | globalThis.Deno.UnsafeFnPointer = ffi.UnsafeFnPointer; -------------------------------------------------------------------------------- /src/ext/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{web::PermissionsContainer, ExtensionTrait}; 2 | use deno_core::{extension, Extension}; 3 | 4 | extension!( 5 | init_ffi, 6 | deps = [rustyscript], 7 | esm_entry_point = "ext:init_ffi/init_ffi.js", 8 | esm = [ dir "src/ext/ffi", "init_ffi.js" ], 9 | ); 10 | impl ExtensionTrait<()> for init_ffi { 11 | fn init((): ()) -> Extension { 12 | init_ffi::init_ops_and_esm() 13 | } 14 | } 15 | impl ExtensionTrait<()> for deno_ffi::deno_ffi { 16 | fn init((): ()) -> Extension { 17 | deno_ffi::deno_ffi::init_ops_and_esm::() 18 | } 19 | } 20 | 21 | pub fn extensions(is_snapshot: bool) -> Vec { 22 | vec![ 23 | deno_ffi::deno_ffi::build((), is_snapshot), 24 | init_ffi::build((), is_snapshot), 25 | ] 26 | } 27 | 28 | impl deno_ffi::FfiPermissions for PermissionsContainer { 29 | fn check_partial_no_path(&mut self) -> Result<(), deno_permissions::PermissionCheckError> { 30 | self.0.check_exec()?; 31 | Ok(()) 32 | } 33 | 34 | fn check_partial_with_path( 35 | &mut self, 36 | path: &str, 37 | ) -> Result { 38 | self.check_partial_no_path()?; 39 | let p = self.0.check_read(std::path::Path::new(path), None)?; 40 | Ok(p.to_path_buf()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ext/fs/init_fs.js: -------------------------------------------------------------------------------- 1 | import * as fs from "ext:deno_fs/30_fs.js"; 2 | 3 | globalThis.Deno.writeFileSync = fs.writeFileSync; 4 | globalThis.Deno.writeFile = fs.writeFile; 5 | globalThis.Deno.writeTextFileSync = fs.writeTextFileSync; 6 | globalThis.Deno.writeTextFile = fs.writeTextFile; 7 | globalThis.Deno.readTextFile = fs.readTextFile; 8 | globalThis.Deno.readTextFileSync = fs.readTextFileSync; 9 | globalThis.Deno.readFile = fs.readFile; 10 | globalThis.Deno.readFileSync = fs.readFileSync; 11 | globalThis.Deno.chmodSync = fs.chmodSync; 12 | globalThis.Deno.chmod = fs.chmod; 13 | globalThis.Deno.chown = fs.chown; 14 | globalThis.Deno.chownSync = fs.chownSync; 15 | globalThis.Deno.copyFileSync = fs.copyFileSync; 16 | globalThis.Deno.cwd = fs.cwd; 17 | globalThis.Deno.makeTempDirSync = fs.makeTempDirSync; 18 | globalThis.Deno.makeTempDir = fs.makeTempDir; 19 | globalThis.Deno.makeTempFileSync = fs.makeTempFileSync; 20 | globalThis.Deno.makeTempFile = fs.makeTempFile; 21 | globalThis.Deno.mkdirSync = fs.mkdirSync; 22 | globalThis.Deno.mkdir = fs.mkdir; 23 | globalThis.Deno.chdir = fs.chdir; 24 | globalThis.Deno.copyFile = fs.copyFile; 25 | globalThis.Deno.readDirSync = fs.readDirSync; 26 | globalThis.Deno.readDir = fs.readDir; 27 | globalThis.Deno.readLinkSync = fs.readLinkSync; 28 | globalThis.Deno.readLink = fs.readLink; 29 | globalThis.Deno.realPathSync = fs.realPathSync; 30 | globalThis.Deno.realPath = fs.realPath; 31 | globalThis.Deno.removeSync = fs.removeSync; 32 | globalThis.Deno.remove = fs.remove; 33 | globalThis.Deno.renameSync = fs.renameSync; 34 | globalThis.Deno.rename = fs.rename; 35 | globalThis.Deno.statSync = fs.statSync; 36 | globalThis.Deno.lstatSync = fs.lstatSync; 37 | globalThis.Deno.stat = fs.stat; 38 | globalThis.Deno.lstat = fs.lstat; 39 | globalThis.Deno.truncateSync = fs.truncateSync; 40 | globalThis.Deno.truncate = fs.truncate; 41 | globalThis.Deno.FsFile = fs.FsFile; 42 | globalThis.Deno.open = fs.open; 43 | globalThis.Deno.openSync = fs.openSync; 44 | globalThis.Deno.create = fs.create; 45 | globalThis.Deno.createSync = fs.createSync; 46 | globalThis.Deno.symlink = fs.symlink; 47 | globalThis.Deno.symlinkSync = fs.symlinkSync; 48 | globalThis.Deno.link = fs.link; 49 | globalThis.Deno.linkSync = fs.linkSync; 50 | globalThis.Deno.utime = fs.utime; 51 | globalThis.Deno.utimeSync = fs.utimeSync; 52 | globalThis.Deno.umask = fs.umask; -------------------------------------------------------------------------------- /src/ext/fs/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use super::{web::PermissionsContainer, ExtensionTrait}; 4 | use deno_core::{extension, Extension}; 5 | use deno_fs::FileSystemRc; 6 | use deno_io::fs::FsError; 7 | use deno_permissions::PermissionCheckError; 8 | 9 | extension!( 10 | init_fs, 11 | deps = [rustyscript], 12 | esm_entry_point = "ext:init_fs/init_fs.js", 13 | esm = [ dir "src/ext/fs", "init_fs.js" ], 14 | ); 15 | impl ExtensionTrait<()> for init_fs { 16 | fn init((): ()) -> Extension { 17 | init_fs::init_ops_and_esm() 18 | } 19 | } 20 | impl ExtensionTrait for deno_fs::deno_fs { 21 | fn init(fs: FileSystemRc) -> Extension { 22 | deno_fs::deno_fs::init_ops_and_esm::(fs) 23 | } 24 | } 25 | 26 | pub fn extensions(fs: FileSystemRc, is_snapshot: bool) -> Vec { 27 | vec![ 28 | deno_fs::deno_fs::build(fs, is_snapshot), 29 | init_fs::build((), is_snapshot), 30 | ] 31 | } 32 | 33 | impl deno_fs::FsPermissions for PermissionsContainer { 34 | fn check_open<'a>( 35 | &mut self, 36 | resolved: bool, 37 | read: bool, 38 | write: bool, 39 | path: &'a std::path::Path, 40 | api_name: &str, 41 | ) -> Result, FsError> { 42 | self.0 43 | .check_open(resolved, read, write, path, api_name) 44 | .ok_or(FsError::NotCapable("Access Denied")) 45 | } 46 | 47 | fn check_read( 48 | &mut self, 49 | path: &str, 50 | api_name: &str, 51 | ) -> Result { 52 | self.0.check_read_all(Some(api_name))?; 53 | let p = self 54 | .0 55 | .check_read(Path::new(path), Some(api_name)) 56 | .map(std::borrow::Cow::into_owned)?; 57 | Ok(p) 58 | } 59 | 60 | fn check_read_path<'a>( 61 | &mut self, 62 | path: &'a std::path::Path, 63 | api_name: &str, 64 | ) -> Result, PermissionCheckError> { 65 | self.0.check_read_all(Some(api_name))?; 66 | let p = self.0.check_read(path, Some(api_name))?; 67 | Ok(p) 68 | } 69 | 70 | fn check_read_all(&mut self, api_name: &str) -> Result<(), PermissionCheckError> { 71 | self.0.check_read_all(Some(api_name))?; 72 | Ok(()) 73 | } 74 | 75 | fn check_read_blind( 76 | &mut self, 77 | p: &std::path::Path, 78 | display: &str, 79 | api_name: &str, 80 | ) -> Result<(), PermissionCheckError> { 81 | self.0.check_read_all(Some(api_name))?; 82 | self.0.check_read_blind(p, display, api_name)?; 83 | Ok(()) 84 | } 85 | 86 | fn check_write( 87 | &mut self, 88 | path: &str, 89 | api_name: &str, 90 | ) -> Result { 91 | self.0.check_write_all(api_name)?; 92 | let p = self 93 | .0 94 | .check_write(Path::new(path), Some(api_name)) 95 | .map(std::borrow::Cow::into_owned)?; 96 | Ok(p) 97 | } 98 | 99 | fn check_write_path<'a>( 100 | &mut self, 101 | path: &'a std::path::Path, 102 | api_name: &str, 103 | ) -> Result, PermissionCheckError> { 104 | self.0.check_write_all(api_name)?; 105 | let p = self.0.check_write(path, Some(api_name))?; 106 | Ok(p) 107 | } 108 | 109 | fn check_write_partial( 110 | &mut self, 111 | path: &str, 112 | api_name: &str, 113 | ) -> Result { 114 | self.0.check_write_all(api_name)?; 115 | let p = self.0.check_write_partial(path, api_name)?; 116 | Ok(p) 117 | } 118 | 119 | fn check_write_all(&mut self, api_name: &str) -> Result<(), PermissionCheckError> { 120 | self.0.check_write_all(api_name)?; 121 | Ok(()) 122 | } 123 | 124 | fn check_write_blind( 125 | &mut self, 126 | p: &std::path::Path, 127 | display: &str, 128 | api_name: &str, 129 | ) -> Result<(), PermissionCheckError> { 130 | self.0.check_write_all(api_name)?; 131 | self.0.check_write_blind(p, display, api_name)?; 132 | Ok(()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/ext/http/http_runtime.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | #![allow(dead_code)] 3 | use std::rc::Rc; 4 | 5 | use deno_core::{error::ResourceError, extension, op2, OpState, ResourceId}; 6 | use deno_http::http_create_conn_resource; 7 | use deno_net::{io::TcpStreamResource, ops_tls::TlsStreamResource}; 8 | 9 | extension!(deno_http_runtime, ops = [op_http_start]); 10 | #[derive(Debug, thiserror::Error, deno_error::JsError)] 11 | pub enum HttpStartError { 12 | #[class("Busy")] 13 | #[error("TCP stream is currently in use")] 14 | TcpStreamInUse, 15 | #[class("Busy")] 16 | #[error("TLS stream is currently in use")] 17 | TlsStreamInUse, 18 | #[class("Busy")] 19 | #[error("Unix socket is currently in use")] 20 | UnixSocketInUse, 21 | #[class(generic)] 22 | #[error(transparent)] 23 | ReuniteTcp(#[from] tokio::net::tcp::ReuniteError), 24 | #[cfg(unix)] 25 | #[class(generic)] 26 | #[error(transparent)] 27 | ReuniteUnix(#[from] tokio::net::unix::ReuniteError), 28 | #[class(inherit)] 29 | #[error("{0}")] 30 | Io( 31 | #[from] 32 | #[inherit] 33 | std::io::Error, 34 | ), 35 | #[class(inherit)] 36 | #[error(transparent)] 37 | Resource(#[inherit] ResourceError), 38 | } 39 | 40 | #[op2(fast)] 41 | #[smi] 42 | fn op_http_start( 43 | state: &mut OpState, 44 | #[smi] tcp_stream_rid: ResourceId, 45 | ) -> Result { 46 | if let Ok(resource_rc) = state 47 | .resource_table 48 | .take::(tcp_stream_rid) 49 | { 50 | // This TCP connection might be used somewhere else. If it's the case, we cannot proceed with the 51 | // process of starting a HTTP server on top of this TCP connection, so we just return a Busy error. 52 | // See also: https://github.com/denoland/deno/pull/16242 53 | let resource = Rc::try_unwrap(resource_rc).map_err(|_| HttpStartError::TcpStreamInUse)?; 54 | let (read_half, write_half) = resource.into_inner(); 55 | let tcp_stream = read_half.reunite(write_half)?; 56 | let addr = tcp_stream.local_addr()?; 57 | return Ok(http_create_conn_resource(state, tcp_stream, addr, "http")); 58 | } 59 | 60 | if let Ok(resource_rc) = state 61 | .resource_table 62 | .take::(tcp_stream_rid) 63 | { 64 | // This TLS connection might be used somewhere else. If it's the case, we cannot proceed with the 65 | // process of starting a HTTP server on top of this TLS connection, so we just return a Busy error. 66 | // See also: https://github.com/denoland/deno/pull/16242 67 | let resource = Rc::try_unwrap(resource_rc).map_err(|_| HttpStartError::TlsStreamInUse)?; 68 | let (read_half, write_half) = resource.into_inner(); 69 | let tls_stream = read_half.unsplit(write_half); 70 | let addr = tls_stream.local_addr()?; 71 | return Ok(http_create_conn_resource(state, tls_stream, addr, "https")); 72 | } 73 | 74 | #[cfg(unix)] 75 | if let Ok(resource_rc) = state 76 | .resource_table 77 | .take::(tcp_stream_rid) 78 | { 79 | // This UNIX socket might be used somewhere else. If it's the case, we cannot proceed with the 80 | // process of starting a HTTP server on top of this UNIX socket, so we just return a Busy error. 81 | // See also: https://github.com/denoland/deno/pull/16242 82 | let resource = Rc::try_unwrap(resource_rc).map_err(|_| HttpStartError::UnixSocketInUse)?; 83 | let (read_half, write_half) = resource.into_inner(); 84 | let unix_stream = read_half.reunite(write_half)?; 85 | let addr = unix_stream.local_addr()?; 86 | return Ok(http_create_conn_resource( 87 | state, 88 | unix_stream, 89 | addr, 90 | "http+unix", 91 | )); 92 | } 93 | 94 | Err(HttpStartError::Resource(ResourceError::BadResourceId)) 95 | } 96 | -------------------------------------------------------------------------------- /src/ext/http/init_http.js: -------------------------------------------------------------------------------- 1 | import * as serve from 'ext:deno_http/00_serve.ts'; 2 | import * as http from 'ext:deno_http/01_http.js'; 3 | import * as websocket from 'ext:deno_http/02_websocket.ts'; 4 | 5 | globalThis.Deno.serve = serve.serve; 6 | globalThis.Deno.serveHttp = http.serveHttp; 7 | globalThis.Deno.upgradeWebSocket = websocket.upgradeWebSocket; -------------------------------------------------------------------------------- /src/ext/http/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | mod http_runtime; 5 | use http_runtime::deno_http_runtime; 6 | impl ExtensionTrait<()> for deno_http_runtime { 7 | fn init((): ()) -> Extension { 8 | deno_http_runtime::init_ops_and_esm() 9 | } 10 | } 11 | 12 | extension!( 13 | init_http, 14 | deps = [rustyscript], 15 | esm_entry_point = "ext:init_http/init_http.js", 16 | esm = [ dir "src/ext/http", "init_http.js" ], 17 | ); 18 | impl ExtensionTrait<()> for init_http { 19 | fn init((): ()) -> Extension { 20 | init_http::init_ops_and_esm() 21 | } 22 | } 23 | impl ExtensionTrait<()> for deno_http::deno_http { 24 | fn init((): ()) -> Extension { 25 | deno_http::deno_http::init_ops_and_esm(deno_http::Options { 26 | http2_builder_hook: None, 27 | http1_builder_hook: None, 28 | }) 29 | } 30 | } 31 | 32 | pub fn extensions((): (), is_snapshot: bool) -> Vec { 33 | vec![ 34 | deno_http_runtime::build((), is_snapshot), 35 | deno_http::deno_http::build((), is_snapshot), 36 | init_http::build((), is_snapshot), 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/ext/io/init_io.js: -------------------------------------------------------------------------------- 1 | import * as io from "ext:deno_io/12_io.js"; 2 | 3 | globalThis.Deno.SeekMode = io.SeekMode; 4 | globalThis.Deno.stdin = io.stdin; 5 | globalThis.Deno.stdout = io.stdout; 6 | globalThis.Deno.stderr = io.stderr; 7 | -------------------------------------------------------------------------------- /src/ext/io/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | #[cfg(windows)] 5 | mod tty_windows; 6 | #[cfg(windows)] 7 | use tty_windows as tty; 8 | 9 | #[cfg(unix)] 10 | mod tty_unix; 11 | #[cfg(unix)] 12 | use tty_unix as tty; 13 | 14 | extension!( 15 | init_io, 16 | deps = [rustyscript], 17 | esm_entry_point = "ext:init_io/init_io.js", 18 | esm = [ dir "src/ext/io", "init_io.js" ], 19 | ); 20 | impl ExtensionTrait<()> for init_io { 21 | fn init((): ()) -> Extension { 22 | init_io::init_ops_and_esm() 23 | } 24 | } 25 | impl ExtensionTrait> for deno_io::deno_io { 26 | fn init(pipes: Option) -> Extension { 27 | deno_io::deno_io::init_ops_and_esm(pipes) 28 | } 29 | } 30 | impl ExtensionTrait<()> for tty::deno_tty { 31 | fn init((): ()) -> Extension { 32 | tty::deno_tty::init_ops_and_esm() 33 | } 34 | } 35 | 36 | pub fn extensions(pipes: Option, is_snapshot: bool) -> Vec { 37 | vec![ 38 | deno_io::deno_io::build(pipes, is_snapshot), 39 | tty::deno_tty::build((), is_snapshot), 40 | init_io::build((), is_snapshot), 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/ext/kv/init_kv.js: -------------------------------------------------------------------------------- 1 | import * as init from 'ext:deno_kv/01_db.ts'; 2 | 3 | globalThis.Deno.openKv = init.openKv; 4 | globalThis.Deno.AtomicOperation = init.AtomicOperation; 5 | globalThis.Deno.KvU64 = init.KvU64; 6 | globalThis.Deno.KvListIterator = init.KvListIterator; -------------------------------------------------------------------------------- /src/ext/napi/init_napi.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rscarson/rustyscript/1f18c83bc4a32c31f6c34cd07f18d4b0c5ab6cf5/src/ext/napi/init_napi.js -------------------------------------------------------------------------------- /src/ext/napi/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{web::PermissionsContainer, ExtensionTrait}; 2 | use deno_core::{extension, Extension}; 3 | 4 | extension!( 5 | init_napi, 6 | deps = [rustyscript], 7 | esm_entry_point = "ext:init_napi/init_napi.js", 8 | esm = [ dir "src/ext/napi", "init_napi.js" ], 9 | ); 10 | impl ExtensionTrait<()> for init_napi { 11 | fn init((): ()) -> Extension { 12 | init_napi::init_ops_and_esm() 13 | } 14 | } 15 | impl ExtensionTrait<()> for deno_napi::deno_napi { 16 | fn init((): ()) -> Extension { 17 | deno_napi::deno_napi::init_ops_and_esm::() 18 | } 19 | } 20 | 21 | pub fn extensions(is_snapshot: bool) -> Vec { 22 | vec![ 23 | deno_napi::deno_napi::build((), is_snapshot), 24 | init_napi::build((), is_snapshot), 25 | ] 26 | } 27 | 28 | impl deno_napi::NapiPermissions for PermissionsContainer { 29 | fn check( 30 | &mut self, 31 | path: &str, 32 | ) -> Result { 33 | let p = self.0.check_read(std::path::Path::new(path), None)?; 34 | Ok(p.to_path_buf()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ext/node/cjs_translator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | use std::borrow::Cow; 4 | use std::cell::RefCell; 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | 8 | use deno_ast::MediaType; 9 | use deno_ast::ModuleSpecifier; 10 | use deno_error::JsErrorBox; 11 | use deno_resolver::npm::DenoInNpmPackageChecker; 12 | use deno_runtime::deno_fs; 13 | use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis; 14 | use node_resolver::analyze::CjsAnalysisExports; 15 | use node_resolver::DenoIsBuiltInNodeModuleChecker; 16 | use serde::Deserialize; 17 | use serde::Serialize; 18 | use sys_traits::impls::RealSys; 19 | 20 | use super::resolvers::RustyNpmPackageFolderResolver; 21 | use super::resolvers::RustyResolver; 22 | 23 | pub type NodeCodeTranslator = node_resolver::analyze::NodeCodeTranslator< 24 | RustyCjsCodeAnalyzer, 25 | DenoInNpmPackageChecker, 26 | DenoIsBuiltInNodeModuleChecker, 27 | RustyNpmPackageFolderResolver, 28 | RealSys, 29 | >; 30 | 31 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 32 | pub enum CjsAnalysis { 33 | /// The module was found to be an ES module. 34 | Esm, 35 | /// The module was CJS. 36 | Cjs { 37 | exports: Vec, 38 | reexports: Vec, 39 | }, 40 | } 41 | impl From> for CjsAnalysis { 42 | fn from(analysis: ExtNodeCjsAnalysis) -> Self { 43 | match analysis { 44 | ExtNodeCjsAnalysis::Esm(_) => CjsAnalysis::Esm, 45 | ExtNodeCjsAnalysis::Cjs(analysis) => CjsAnalysis::Cjs { 46 | exports: analysis.exports, 47 | reexports: analysis.reexports, 48 | }, 49 | } 50 | } 51 | } 52 | impl From for CjsAnalysis { 53 | fn from(analysis: deno_ast::CjsAnalysis) -> Self { 54 | Self::Cjs { 55 | exports: analysis.exports, 56 | reexports: analysis.reexports, 57 | } 58 | } 59 | } 60 | 61 | pub struct RustyCjsCodeAnalyzer { 62 | fs: deno_fs::FileSystemRc, 63 | cache: RefCell>, 64 | cjs_tracker: Arc, 65 | } 66 | 67 | impl RustyCjsCodeAnalyzer { 68 | pub fn new(fs: deno_fs::FileSystemRc, cjs_tracker: Arc) -> Self { 69 | Self { 70 | fs, 71 | cache: RefCell::new(HashMap::new()), 72 | cjs_tracker, 73 | } 74 | } 75 | 76 | fn inner_cjs_analysis( 77 | &self, 78 | specifier: &ModuleSpecifier, 79 | source: &str, 80 | ) -> Result { 81 | if let Some(analysis) = self.cache.borrow().get(specifier.as_str()) { 82 | return Ok(analysis.clone()); 83 | } 84 | 85 | let media_type = MediaType::from_specifier(specifier); 86 | if media_type == MediaType::Json { 87 | return Ok(CjsAnalysis::Cjs { 88 | exports: vec![], 89 | reexports: vec![], 90 | }); 91 | } 92 | 93 | let parsed_source = deno_ast::parse_program(deno_ast::ParseParams { 94 | specifier: specifier.clone(), 95 | text: source.into(), 96 | media_type, 97 | capture_tokens: true, 98 | scope_analysis: false, 99 | maybe_syntax: None, 100 | }) 101 | .map_err(JsErrorBox::from_err)?; 102 | let is_script = parsed_source.compute_is_script(); 103 | let is_cjs = self 104 | .cjs_tracker 105 | .is_cjs(parsed_source.specifier(), media_type, is_script); 106 | let analysis = if is_cjs { 107 | parsed_source.analyze_cjs().into() 108 | } else { 109 | CjsAnalysis::Esm 110 | }; 111 | 112 | self.cache 113 | .borrow_mut() 114 | .insert(specifier.as_str().to_string(), analysis.clone()); 115 | 116 | Ok(analysis) 117 | } 118 | 119 | fn analyze_cjs<'a>( 120 | &self, 121 | specifier: &ModuleSpecifier, 122 | source: Cow<'a, str>, 123 | ) -> Result, JsErrorBox> { 124 | let analysis = self.inner_cjs_analysis(specifier, &source)?; 125 | match analysis { 126 | CjsAnalysis::Esm => Ok(ExtNodeCjsAnalysis::Esm(source)), 127 | CjsAnalysis::Cjs { exports, reexports } => { 128 | Ok(ExtNodeCjsAnalysis::Cjs(CjsAnalysisExports { 129 | exports, 130 | reexports, 131 | })) 132 | } 133 | } 134 | } 135 | } 136 | 137 | #[async_trait::async_trait(?Send)] 138 | impl node_resolver::analyze::CjsCodeAnalyzer for RustyCjsCodeAnalyzer { 139 | async fn analyze_cjs<'a>( 140 | &self, 141 | specifier: &ModuleSpecifier, 142 | source: Option>, 143 | ) -> Result, JsErrorBox> { 144 | let source = match source { 145 | Some(source) => source, 146 | None => { 147 | if let Ok(path) = specifier.to_file_path() { 148 | if let Ok(source_from_file) = 149 | self.fs.read_text_file_lossy_async(path, None).await 150 | { 151 | source_from_file 152 | } else { 153 | return Ok(ExtNodeCjsAnalysis::Cjs(CjsAnalysisExports { 154 | exports: vec![], 155 | reexports: vec![], 156 | })); 157 | } 158 | } else { 159 | return Ok(ExtNodeCjsAnalysis::Cjs(CjsAnalysisExports { 160 | exports: vec![], 161 | reexports: vec![], 162 | })); 163 | } 164 | } 165 | }; 166 | 167 | self.analyze_cjs(specifier, source) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/ext/node/init_node.js: -------------------------------------------------------------------------------- 1 | import { initializeDebugEnv } from "ext:deno_node/internal/util/debuglog.ts"; 2 | initializeDebugEnv("rustyscript"); -------------------------------------------------------------------------------- /src/ext/node/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | web::{PermissionsContainer, SystemsPermissionKind}, 3 | ExtensionTrait, 4 | }; 5 | use deno_core::{extension, Extension}; 6 | use deno_node::NodePermissions; 7 | use deno_permissions::PermissionCheckError; 8 | use deno_resolver::npm::DenoInNpmPackageChecker; 9 | use resolvers::{RustyNpmPackageFolderResolver, RustyResolver}; 10 | use std::{path::Path, sync::Arc}; 11 | use sys_traits::impls::RealSys; 12 | 13 | mod cjs_translator; 14 | pub mod resolvers; 15 | pub use cjs_translator::NodeCodeTranslator; 16 | 17 | extension!( 18 | init_node, 19 | deps = [rustyscript], 20 | esm_entry_point = "ext:init_node/init_node.js", 21 | esm = [ dir "src/ext/node", "init_node.js" ], 22 | ); 23 | impl ExtensionTrait<()> for init_node { 24 | fn init((): ()) -> Extension { 25 | init_node::init_ops_and_esm() 26 | } 27 | } 28 | impl ExtensionTrait> for deno_node::deno_node { 29 | fn init(resolver: Arc) -> Extension { 30 | deno_node::deno_node::init_ops_and_esm::< 31 | PermissionsContainer, 32 | DenoInNpmPackageChecker, 33 | RustyNpmPackageFolderResolver, 34 | RealSys, 35 | >(Some(resolver.init_services()), resolver.filesystem()) 36 | } 37 | } 38 | 39 | pub fn extensions(resolver: Arc, is_snapshot: bool) -> Vec { 40 | vec![ 41 | deno_node::deno_node::build(resolver, is_snapshot), 42 | init_node::build((), is_snapshot), 43 | ] 44 | } 45 | 46 | impl NodePermissions for PermissionsContainer { 47 | fn check_net( 48 | &mut self, 49 | host: (&str, Option), 50 | api_name: &str, 51 | ) -> Result<(), PermissionCheckError> { 52 | self.0.check_host(host.0, host.1, api_name)?; 53 | Ok(()) 54 | } 55 | 56 | fn check_read(&mut self, path: &str) -> Result { 57 | let p = self.0.check_read(Path::new(path), None)?; 58 | Ok(p.into_owned()) 59 | } 60 | 61 | fn check_net_url( 62 | &mut self, 63 | url: &reqwest::Url, 64 | api_name: &str, 65 | ) -> std::result::Result<(), PermissionCheckError> { 66 | self.0.check_url(url, api_name)?; 67 | Ok(()) 68 | } 69 | 70 | fn check_read_with_api_name( 71 | &mut self, 72 | path: &str, 73 | api_name: Option<&str>, 74 | ) -> Result { 75 | let p = self 76 | .0 77 | .check_read(Path::new(path), api_name) 78 | .map(std::borrow::Cow::into_owned)?; 79 | Ok(p) 80 | } 81 | 82 | fn check_read_path<'a>( 83 | &mut self, 84 | path: &'a std::path::Path, 85 | ) -> Result, PermissionCheckError> { 86 | let p = self.0.check_read(path, None)?; 87 | Ok(p) 88 | } 89 | 90 | fn query_read_all(&mut self) -> bool { 91 | self.0.check_read_all(None).is_ok() 92 | } 93 | 94 | fn check_sys(&mut self, kind: &str, api_name: &str) -> Result<(), PermissionCheckError> { 95 | let kind = SystemsPermissionKind::new(kind); 96 | self.0.check_sys(kind, api_name)?; 97 | Ok(()) 98 | } 99 | 100 | fn check_write_with_api_name( 101 | &mut self, 102 | path: &str, 103 | api_name: Option<&str>, 104 | ) -> Result { 105 | let p = self 106 | .0 107 | .check_write(Path::new(path), api_name) 108 | .map(std::borrow::Cow::into_owned)?; 109 | Ok(p) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/ext/runtime/init_runtime.js: -------------------------------------------------------------------------------- 1 | import * as util from 'ext:runtime/06_util.js'; 2 | import * as permissions from 'ext:runtime/10_permissions.js'; 3 | import * as workers from 'ext:runtime/11_workers.js'; 4 | import * as os from 'ext:deno_os/30_os.js'; 5 | import * as process from 'ext:deno_process/40_process.js'; 6 | import * as prompt from 'ext:runtime/41_prompt.js'; 7 | import * as scope from 'ext:runtime/98_global_scope_shared.js'; 8 | import * as scopeWorker from 'ext:runtime/98_global_scope_worker.js'; 9 | import * as scopeWindow from 'ext:runtime/98_global_scope_window.js'; 10 | 11 | import * as errors from "ext:runtime/01_errors.js"; 12 | import * as version from "ext:runtime/01_version.ts"; 13 | import * as signals from "ext:deno_os/40_signals.js"; 14 | import * as tty from "ext:runtime/40_tty.js"; 15 | 16 | const opArgs = scopeWindow.memoizeLazy(() => core.ops.op_bootstrap_args()); 17 | const opPid = scopeWindow.memoizeLazy(() => core.ops.op_bootstrap_pid()); 18 | 19 | import { core } from "ext:core/mod.js"; 20 | import { applyToDeno, getterOnly, readOnly, nonEnumerable } from "ext:rustyscript/rustyscript.js"; 21 | 22 | //applyToDeno(denoNs); 23 | applyToDeno({ 24 | pid: getterOnly(opPid), 25 | 26 | // `ppid` should not be memoized. 27 | // https://github.com/denoland/deno/issues/23004 28 | ppid: getterOnly(() => core.ops.op_ppid()), 29 | noColor: getterOnly(() => core.ops.op_bootstrap_no_color()), 30 | args: getterOnly(opArgs), 31 | mainModule: getterOnly(() => core.ops.op_main_module()), 32 | exitCode: { 33 | __proto__: null, 34 | get() { 35 | return os.getExitCode(); 36 | }, 37 | set(value) { 38 | os.setExitCode(value); 39 | }, 40 | }, 41 | 42 | Process: nonEnumerable(process.Process), 43 | run: nonEnumerable(process.run), 44 | kill: nonEnumerable(process.kill), 45 | Command: nonEnumerable(process.Command), 46 | ChildProcess: nonEnumerable(process.ChildProcess), 47 | 48 | isatty: nonEnumerable(tty.isatty), 49 | consoleSize: nonEnumerable(tty.consoleSize), 50 | 51 | memoryUsage: nonEnumerable(() => op_runtime_memory_usage()), 52 | version: nonEnumerable(version.version), 53 | build: nonEnumerable(core.build), 54 | errors: nonEnumerable(errors.errors), 55 | 56 | permissions: nonEnumerable(permissions.permissions), 57 | Permissions: nonEnumerable(permissions.Permissions), 58 | PermissionStatus: nonEnumerable(permissions.PermissionStatus), 59 | 60 | addSignalListener: nonEnumerable(signals.addSignalListener), 61 | removeSignalListener: nonEnumerable(signals.removeSignalListener), 62 | 63 | env: nonEnumerable(os.env), 64 | exit: nonEnumerable(os.exit), 65 | execPath: nonEnumerable(os.execPath), 66 | loadavg: nonEnumerable(os.loadavg), 67 | osRelease: nonEnumerable(os.osRelease), 68 | osUptime: nonEnumerable(os.osUptime), 69 | hostname: nonEnumerable(os.hostname), 70 | systemMemoryInfo: nonEnumerable(os.systemMemoryInfo), 71 | networkInterfaces: nonEnumerable(os.networkInterfaces), 72 | 73 | gid: nonEnumerable(os.gid), 74 | uid: nonEnumerable(os.uid), 75 | 76 | 77 | core: readOnly(core), 78 | }); 79 | 80 | import * as _console from 'ext:deno_console/01_console.js'; 81 | _console.setNoColorFns( 82 | () => globalThis.Deno.core.ops.op_bootstrap_no_color() || !globalThis.Deno.core.ops.op_bootstrap_is_stdout_tty(), 83 | () => globalThis.Deno.core.ops.op_bootstrap_no_color() || !globalThis.Deno.core.ops.op_bootstrap_is_stderr_tty(), 84 | ); 85 | -------------------------------------------------------------------------------- /src/ext/rustyscript/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use crate::{error::Error, RsAsyncFunction, RsFunction}; 3 | use deno_core::{extension, op2, serde_json, v8, Extension, OpState}; 4 | use std::collections::HashMap; 5 | 6 | type FnCache = HashMap>; 7 | type AsyncFnCache = HashMap>; 8 | 9 | mod callbacks; 10 | 11 | /// Registers a JS function with the runtime as being the entrypoint for the module 12 | /// 13 | /// # Arguments 14 | /// * `state` - The runtime's state, into which the function will be put 15 | /// * `callback` - The function to register 16 | #[op2] 17 | fn op_register_entrypoint(state: &mut OpState, #[global] callback: v8::Global) { 18 | state.put(callback); 19 | } 20 | 21 | #[op2] 22 | #[serde] 23 | #[allow(clippy::needless_pass_by_value)] 24 | fn call_registered_function( 25 | #[string] name: &str, 26 | #[serde] args: Vec, 27 | state: &mut OpState, 28 | ) -> Result { 29 | if state.has::() { 30 | let table = state.borrow_mut::(); 31 | if let Some(callback) = table.get(name) { 32 | return callback(&args); 33 | } 34 | } 35 | 36 | Err(Error::ValueNotCallable(name.to_string())) 37 | } 38 | 39 | #[op2(async)] 40 | #[serde] 41 | fn call_registered_function_async( 42 | #[string] name: String, 43 | #[serde] args: Vec, 44 | state: &mut OpState, 45 | ) -> impl std::future::Future> { 46 | if state.has::() { 47 | let table = state.borrow_mut::(); 48 | if let Some(callback) = table.get(&name) { 49 | return callback(args); 50 | } 51 | } 52 | 53 | Box::pin(std::future::ready(Err(Error::ValueNotCallable(name)))) 54 | } 55 | 56 | #[op2(fast)] 57 | fn op_panic2(#[string] msg: &str) -> Result<(), Error> { 58 | Err(Error::Runtime(msg.to_string())) 59 | } 60 | 61 | extension!( 62 | rustyscript, 63 | ops = [op_register_entrypoint, call_registered_function, call_registered_function_async], 64 | esm_entry_point = "ext:rustyscript/rustyscript.js", 65 | esm = [ dir "src/ext/rustyscript", "rustyscript.js" ], 66 | middleware = |op| match op.name { 67 | "op_panic" => op.with_implementation_from(&op_panic2()), 68 | _ => op, 69 | } 70 | ); 71 | impl ExtensionTrait<()> for rustyscript { 72 | fn init(options: ()) -> Extension { 73 | rustyscript::init_ops_and_esm() 74 | } 75 | } 76 | 77 | pub fn extensions(is_snapshot: bool) -> Vec { 78 | vec![rustyscript::build((), is_snapshot)] 79 | } 80 | -------------------------------------------------------------------------------- /src/ext/rustyscript/rustyscript.js: -------------------------------------------------------------------------------- 1 | // Loaders used by other extensions 2 | const ObjectProperties = { 3 | 'nonEnumerable': {writable: true, enumerable: false, configurable: true}, 4 | 'readOnly': {writable: false, enumerable: false, configurable: true}, 5 | 'writeable': {writable: true, enumerable: true, configurable: true}, 6 | 'getterOnly': {enumerable: true, configurable: true}, 7 | 8 | 'apply': (value, type) => { 9 | return { 10 | 'value': value, 11 | ...ObjectProperties[type] 12 | }; 13 | } 14 | } 15 | const nonEnumerable = (value) => ObjectProperties.apply(value, nonEnumerable); 16 | const readOnly = (value) => ObjectProperties.apply(value, readOnly); 17 | const writeable = (value) => ObjectProperties.apply(value, writeable); 18 | const getterOnly = (getter) => { 19 | return { 20 | get: getter, 21 | set() {}, 22 | ...ObjectProperties.getterOnly 23 | }; 24 | } 25 | const applyToGlobal = (properties) => Object.defineProperties(globalThis, properties); 26 | const applyToDeno = (properties) => Object.defineProperties(globalThis.Deno, properties); 27 | 28 | // Populate the global object 29 | globalThis.rustyscript = { 30 | 'register_entrypoint': (f) => Deno.core.ops.op_register_entrypoint(f), 31 | 'bail': (msg) => { throw new Error(msg) }, 32 | 33 | 'functions': new Proxy({}, { 34 | get: function(_target, name) { 35 | return (...args) => Deno.core.ops.call_registered_function(name, args); 36 | } 37 | }), 38 | 39 | 'async_functions': new Proxy({}, { 40 | get: function(_target, name) { 41 | return (...args) => Deno.core.ops.call_registered_function_async(name, args); 42 | } 43 | }) 44 | }; 45 | Object.freeze(globalThis.rustyscript); 46 | 47 | export { 48 | nonEnumerable, readOnly, writeable, getterOnly, applyToGlobal, applyToDeno 49 | }; -------------------------------------------------------------------------------- /src/ext/url/init_url.js: -------------------------------------------------------------------------------- 1 | import * as url from 'ext:deno_url/00_url.js'; 2 | import * as urlPattern from 'ext:deno_url/01_urlpattern.js'; 3 | 4 | import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 5 | applyToGlobal({ 6 | URL: nonEnumerable(url.URL), 7 | URLPattern: nonEnumerable(urlPattern.URLPattern), 8 | URLSearchParams: nonEnumerable(url.URLSearchParams), 9 | }); -------------------------------------------------------------------------------- /src/ext/url/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | extension!( 5 | init_url, 6 | deps = [rustyscript], 7 | esm_entry_point = "ext:init_url/init_url.js", 8 | esm = [ dir "src/ext/url", "init_url.js" ], 9 | ); 10 | impl ExtensionTrait<()> for init_url { 11 | fn init((): ()) -> Extension { 12 | init_url::init_ops_and_esm() 13 | } 14 | } 15 | impl ExtensionTrait<()> for deno_url::deno_url { 16 | fn init((): ()) -> Extension { 17 | deno_url::deno_url::init_ops_and_esm() 18 | } 19 | } 20 | 21 | pub fn extensions(is_snapshot: bool) -> Vec { 22 | vec![ 23 | deno_url::deno_url::build((), is_snapshot), 24 | init_url::build((), is_snapshot), 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/ext/web/init_fetch.js: -------------------------------------------------------------------------------- 1 | import * as headers from "ext:deno_fetch/20_headers.js"; 2 | import * as formData from "ext:deno_fetch/21_formdata.js"; 3 | import * as httpClient from "ext:deno_fetch/22_http_client.js"; 4 | import * as request from "ext:deno_fetch/23_request.js"; 5 | import * as response from "ext:deno_fetch/23_response.js"; 6 | import * as fetch from "ext:deno_fetch/26_fetch.js"; 7 | import * as eventSource from "ext:deno_fetch/27_eventsource.js"; 8 | 9 | Deno.core.setWasmStreamingCallback(fetch.handleWasmStreaming); 10 | 11 | import {applyToGlobal, writeable, nonEnumerable} from 'ext:rustyscript/rustyscript.js'; 12 | 13 | applyToGlobal({ 14 | fetch: writeable(fetch.fetch), 15 | Request: nonEnumerable(request.Request), 16 | Response: nonEnumerable(response.Response), 17 | Headers: nonEnumerable(headers.Headers), 18 | FormData: nonEnumerable(formData.FormData), 19 | EventSource: nonEnumerable(eventSource.EventSource) 20 | }); 21 | 22 | globalThis.Deno.HttpClient = httpClient.HttpClient; 23 | globalThis.Deno.createHttpClient = httpClient.createHttpClient; -------------------------------------------------------------------------------- /src/ext/web/init_net.js: -------------------------------------------------------------------------------- 1 | 2 | import * as net from "ext:deno_net/01_net.js"; 3 | import * as tls from "ext:deno_net/02_tls.js"; 4 | 5 | globalThis.Deno.connect = net.connect; 6 | globalThis.Deno.listen = net.listen; 7 | globalThis.Deno.resolveDns = net.resolveDns; 8 | 9 | import { 10 | op_net_listen_udp, 11 | op_net_listen_unixpacket, 12 | } from "ext:core/ops"; 13 | globalThis.Deno.listenDatagram = net.createListenDatagram( 14 | op_net_listen_udp, 15 | op_net_listen_unixpacket, 16 | ); 17 | 18 | globalThis.Deno.connectTls = tls.connectTls; 19 | globalThis.Deno.listenTls = tls.listenTls; 20 | globalThis.Deno.startTls = tls.startTls; -------------------------------------------------------------------------------- /src/ext/web/init_telemetry.js: -------------------------------------------------------------------------------- 1 | import * as telemetry from "ext:deno_telemetry/telemetry.ts"; 2 | import * as util from "ext:deno_telemetry/util.ts"; 3 | 4 | globalThis.Deno.telemetry = telemetry.telemetry; -------------------------------------------------------------------------------- /src/ext/web/init_web.js: -------------------------------------------------------------------------------- 1 | import * as infra from 'ext:deno_web/00_infra.js'; 2 | import { DOMException } from "ext:deno_web/01_dom_exception.js"; 3 | import * as mimesniff from 'ext:deno_web/01_mimesniff.js'; 4 | import * as event from 'ext:deno_web/02_event.js'; 5 | import * as structuredClone from 'ext:deno_web/02_structured_clone.js'; 6 | import * as timers from 'ext:deno_web/02_timers.js'; 7 | import * as abortSignal from 'ext:deno_web/03_abort_signal.js'; 8 | import * as globalInterfaces from 'ext:deno_web/04_global_interfaces.js'; 9 | import * as base64 from 'ext:deno_web/05_base64.js'; 10 | import * as streams from 'ext:deno_web/06_streams.js'; 11 | import * as encoding from 'ext:deno_web/08_text_encoding.js'; 12 | import * as file from 'ext:deno_web/09_file.js'; 13 | import * as fileReader from 'ext:deno_web/10_filereader.js'; 14 | import * as location from 'ext:deno_web/12_location.js'; 15 | import * as messagePort from 'ext:deno_web/13_message_port.js'; 16 | import * as compression from 'ext:deno_web/14_compression.js'; 17 | import * as performance from 'ext:deno_web/15_performance.js'; 18 | import * as imageData from 'ext:deno_web/16_image_data.js'; 19 | 20 | import * as errors from 'ext:init_web/init_errors.js'; 21 | 22 | globalThis.Deno.refTimer = timers.refTimer; 23 | globalThis.Deno.unrefTimer = timers.unrefTimer; 24 | 25 | import { applyToGlobal, nonEnumerable, writeable } from 'ext:rustyscript/rustyscript.js'; 26 | applyToGlobal({ 27 | AbortController: nonEnumerable(abortSignal.AbortController), 28 | AbortSignal: nonEnumerable(abortSignal.AbortSignal), 29 | Blob: nonEnumerable(file.Blob), 30 | ByteLengthQueuingStrategy: nonEnumerable( 31 | streams.ByteLengthQueuingStrategy, 32 | ), 33 | CloseEvent: nonEnumerable(event.CloseEvent), 34 | CompressionStream: nonEnumerable(compression.CompressionStream), 35 | CountQueuingStrategy: nonEnumerable( 36 | streams.CountQueuingStrategy, 37 | ), 38 | CustomEvent: nonEnumerable(event.CustomEvent), 39 | DecompressionStream: nonEnumerable(compression.DecompressionStream), 40 | DOMException: nonEnumerable(DOMException), 41 | ErrorEvent: nonEnumerable(event.ErrorEvent), 42 | Event: nonEnumerable(event.Event), 43 | EventTarget: nonEnumerable(event.EventTarget), 44 | File: nonEnumerable(file.File), 45 | FileReader: nonEnumerable(fileReader.FileReader), 46 | MessageEvent: nonEnumerable(event.MessageEvent), 47 | Performance: nonEnumerable(performance.Performance), 48 | PerformanceEntry: nonEnumerable(performance.PerformanceEntry), 49 | PerformanceMark: nonEnumerable(performance.PerformanceMark), 50 | PerformanceMeasure: nonEnumerable(performance.PerformanceMeasure), 51 | PromiseRejectionEvent: nonEnumerable(event.PromiseRejectionEvent), 52 | ProgressEvent: nonEnumerable(event.ProgressEvent), 53 | ReadableStream: nonEnumerable(streams.ReadableStream), 54 | ReadableStreamDefaultReader: nonEnumerable( 55 | streams.ReadableStreamDefaultReader, 56 | ), 57 | TextDecoder: nonEnumerable(encoding.TextDecoder), 58 | TextEncoder: nonEnumerable(encoding.TextEncoder), 59 | TextDecoderStream: nonEnumerable(encoding.TextDecoderStream), 60 | TextEncoderStream: nonEnumerable(encoding.TextEncoderStream), 61 | TransformStream: nonEnumerable(streams.TransformStream), 62 | MessageChannel: nonEnumerable(messagePort.MessageChannel), 63 | MessagePort: nonEnumerable(messagePort.MessagePort), 64 | WritableStream: nonEnumerable(streams.WritableStream), 65 | WritableStreamDefaultWriter: nonEnumerable( 66 | streams.WritableStreamDefaultWriter, 67 | ), 68 | WritableStreamDefaultController: nonEnumerable( 69 | streams.WritableStreamDefaultController, 70 | ), 71 | ReadableByteStreamController: nonEnumerable( 72 | streams.ReadableByteStreamController, 73 | ), 74 | ReadableStreamBYOBReader: nonEnumerable( 75 | streams.ReadableStreamBYOBReader, 76 | ), 77 | ReadableStreamBYOBRequest: nonEnumerable( 78 | streams.ReadableStreamBYOBRequest, 79 | ), 80 | ReadableStreamDefaultController: nonEnumerable( 81 | streams.ReadableStreamDefaultController, 82 | ), 83 | TransformStreamDefaultController: nonEnumerable( 84 | streams.TransformStreamDefaultController, 85 | ), 86 | atob: writeable(base64.atob), 87 | btoa: writeable(base64.btoa), 88 | clearInterval: writeable(timers.clearInterval), 89 | clearTimeout: writeable(timers.clearTimeout), 90 | performance: writeable(performance.performance), 91 | reportError: writeable(event.reportError), 92 | setInterval: writeable(timers.setInterval), 93 | setTimeout: writeable(timers.setTimeout), 94 | refTimer: writeable(timers.refTimer), 95 | setImmediate: writeable(timers.setImmediate), 96 | setInterval: writeable(timers.setInterval), 97 | setTimeout: writeable(timers.setTimeout), 98 | unrefTimer: writeable(timers.unrefTimer), 99 | 100 | structuredClone: writeable(messagePort.structuredClone), 101 | ImageData: nonEnumerable(imageData.ImageData), 102 | }); -------------------------------------------------------------------------------- /src/ext/web/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | use std::sync::Arc; 4 | 5 | mod options; 6 | pub use options::WebOptions; 7 | 8 | mod permissions; 9 | pub(crate) use permissions::PermissionsContainer; 10 | pub use permissions::{ 11 | AllowlistWebPermissions, DefaultWebPermissions, PermissionDeniedError, SystemsPermissionKind, 12 | WebPermissions, 13 | }; 14 | 15 | extension!( 16 | init_fetch, 17 | deps = [rustyscript], 18 | esm_entry_point = "ext:init_fetch/init_fetch.js", 19 | esm = [ dir "src/ext/web", "init_fetch.js" ], 20 | ); 21 | impl ExtensionTrait for init_fetch { 22 | fn init(options: WebOptions) -> Extension { 23 | init_fetch::init_ops_and_esm() 24 | } 25 | } 26 | impl ExtensionTrait for deno_fetch::deno_fetch { 27 | fn init(options: WebOptions) -> Extension { 28 | let options = deno_fetch::Options { 29 | user_agent: options.user_agent.clone(), 30 | root_cert_store_provider: options.root_cert_store_provider.clone(), 31 | proxy: options.proxy.clone(), 32 | request_builder_hook: options.request_builder_hook, 33 | unsafely_ignore_certificate_errors: options.unsafely_ignore_certificate_errors.clone(), 34 | client_cert_chain_and_key: options.client_cert_chain_and_key.clone(), 35 | file_fetch_handler: options.file_fetch_handler.clone(), 36 | client_builder_hook: options.client_builder_hook, 37 | resolver: options.resolver.clone(), 38 | }; 39 | 40 | deno_fetch::deno_fetch::init_ops_and_esm::(options) 41 | } 42 | } 43 | 44 | extension!( 45 | init_net, 46 | deps = [rustyscript], 47 | esm_entry_point = "ext:init_net/init_net.js", 48 | esm = [ dir "src/ext/web", "init_net.js" ], 49 | ); 50 | impl ExtensionTrait for init_net { 51 | fn init(options: WebOptions) -> Extension { 52 | init_net::init_ops_and_esm() 53 | } 54 | } 55 | impl ExtensionTrait for deno_net::deno_net { 56 | fn init(options: WebOptions) -> Extension { 57 | deno_net::deno_net::init_ops_and_esm::( 58 | options.root_cert_store_provider.clone(), 59 | options.unsafely_ignore_certificate_errors.clone(), 60 | ) 61 | } 62 | } 63 | 64 | extension!( 65 | init_telemetry, 66 | deps = [rustyscript], 67 | esm_entry_point = "ext:init_telemetry/init_telemetry.js", 68 | esm = [ dir "src/ext/web", "init_telemetry.js" ], 69 | ); 70 | impl ExtensionTrait<()> for init_telemetry { 71 | fn init((): ()) -> Extension { 72 | init_telemetry::init_ops_and_esm() 73 | } 74 | } 75 | 76 | impl ExtensionTrait<()> for deno_telemetry::deno_telemetry { 77 | fn init((): ()) -> Extension { 78 | deno_telemetry::deno_telemetry::init_ops_and_esm() 79 | } 80 | } 81 | 82 | extension!( 83 | init_web, 84 | deps = [rustyscript], 85 | esm_entry_point = "ext:init_web/init_web.js", 86 | esm = [ dir "src/ext/web", "init_web.js", "init_errors.js" ], 87 | options = { 88 | permissions: Arc 89 | }, 90 | state = |state, config| state.put(PermissionsContainer(config.permissions)), 91 | ); 92 | impl ExtensionTrait for init_web { 93 | fn init(options: WebOptions) -> Extension { 94 | init_web::init_ops_and_esm(options.permissions) 95 | } 96 | } 97 | 98 | impl ExtensionTrait for deno_web::deno_web { 99 | fn init(options: WebOptions) -> Extension { 100 | deno_web::deno_web::init_ops_and_esm::( 101 | options.blob_store, 102 | options.base_url, 103 | ) 104 | } 105 | } 106 | 107 | impl ExtensionTrait<()> for deno_tls::deno_tls { 108 | fn init((): ()) -> Extension { 109 | deno_tls::deno_tls::init_ops_and_esm() 110 | } 111 | } 112 | 113 | pub fn extensions(options: WebOptions, is_snapshot: bool) -> Vec { 114 | vec![ 115 | deno_web::deno_web::build(options.clone(), is_snapshot), 116 | deno_telemetry::deno_telemetry::build((), is_snapshot), 117 | deno_net::deno_net::build(options.clone(), is_snapshot), 118 | deno_fetch::deno_fetch::build(options.clone(), is_snapshot), 119 | deno_tls::deno_tls::build((), is_snapshot), 120 | init_web::build(options.clone(), is_snapshot), 121 | init_telemetry::build((), is_snapshot), 122 | init_net::build(options.clone(), is_snapshot), 123 | init_fetch::build(options, is_snapshot), 124 | ] 125 | } 126 | -------------------------------------------------------------------------------- /src/ext/web/options.rs: -------------------------------------------------------------------------------- 1 | use super::{DefaultWebPermissions, WebPermissions}; 2 | use deno_fetch::dns::Resolver; 3 | use hyper_util::client::legacy::Builder; 4 | use std::sync::Arc; 5 | 6 | /// Options for configuring the web related extensions 7 | #[derive(Clone)] 8 | pub struct WebOptions { 9 | /// Base URL for some `deno_web` OPs 10 | pub base_url: Option, 11 | 12 | /// User agent to use for fetch 13 | pub user_agent: String, 14 | 15 | /// Root certificate store for TLS connections for fetches and network OPs 16 | pub root_cert_store_provider: Option>, 17 | 18 | /// Proxy for fetch 19 | pub proxy: Option, 20 | 21 | /// Request builder hook for fetch 22 | #[allow(clippy::type_complexity)] 23 | pub request_builder_hook: 24 | Option) -> Result<(), deno_error::JsErrorBox>>, 25 | 26 | /// List of domain names or IP addresses for which fetches and network OPs will ignore SSL errors 27 | /// 28 | /// This is useful for testing with self-signed certificates 29 | pub unsafely_ignore_certificate_errors: Option>, 30 | 31 | /// Client certificate and key for fetch 32 | pub client_cert_chain_and_key: deno_tls::TlsKeys, 33 | 34 | /// File fetch handler for fetch 35 | pub file_fetch_handler: std::rc::Rc, 36 | 37 | /// Permissions manager for sandbox-breaking extensions 38 | pub permissions: Arc, 39 | 40 | /// Blob store for the web related extensions 41 | pub blob_store: Arc, 42 | 43 | ///A callback to customize HTTP client configuration. 44 | /// 45 | /// For more info on what can be configured, see [`hyper_util::client::legacy::Builder`] 46 | pub client_builder_hook: Option Builder>, 47 | 48 | /// Resolver for DNS resolution 49 | pub resolver: Resolver, 50 | 51 | /// OpenTelemetry configuration for the `deno_telemetry` extension 52 | pub telemetry_config: deno_telemetry::OtelConfig, 53 | } 54 | 55 | impl Default for WebOptions { 56 | fn default() -> Self { 57 | Self { 58 | base_url: None, 59 | user_agent: String::new(), 60 | root_cert_store_provider: None, 61 | proxy: None, 62 | request_builder_hook: None, 63 | unsafely_ignore_certificate_errors: None, 64 | client_cert_chain_and_key: deno_tls::TlsKeys::Null, 65 | file_fetch_handler: std::rc::Rc::new(deno_fetch::DefaultFileFetchHandler), 66 | permissions: Arc::new(DefaultWebPermissions), 67 | blob_store: Arc::new(deno_web::BlobStore::default()), 68 | client_builder_hook: None, 69 | resolver: Resolver::default(), 70 | telemetry_config: deno_telemetry::OtelConfig::default(), 71 | } 72 | } 73 | } 74 | 75 | impl WebOptions { 76 | /// Whitelist a domain or IP for ignoring certificate errors 77 | /// This is useful for testing with self-signed certificates 78 | pub fn whitelist_certificate_for(&mut self, domain_or_ip: impl ToString) { 79 | if let Some(ref mut domains) = self.unsafely_ignore_certificate_errors { 80 | domains.push(domain_or_ip.to_string()); 81 | } else { 82 | self.unsafely_ignore_certificate_errors = Some(vec![domain_or_ip.to_string()]); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ext/web_stub/02_timers.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | import { core, primordials } from "ext:core/mod.js"; 4 | import { op_defer, op_now } from "ext:core/ops"; 5 | const { 6 | Uint8Array, 7 | Uint32Array, 8 | PromisePrototypeThen, 9 | TypedArrayPrototypeGetBuffer, 10 | TypeError, 11 | indirectEval, 12 | ReflectApply, 13 | } = primordials; 14 | const { 15 | getAsyncContext, 16 | setAsyncContext, 17 | } = core; 18 | 19 | import * as webidl from "ext:deno_webidl/00_webidl.js"; 20 | 21 | const hrU8 = new Uint8Array(8); 22 | const hr = new Uint32Array(TypedArrayPrototypeGetBuffer(hrU8)); 23 | function opNow() { 24 | op_now(hrU8); 25 | return (hr[0] * 1000 + hr[1] / 1e6); 26 | } 27 | 28 | // --------------------------------------------------------------------------- 29 | 30 | function checkThis(thisArg) { 31 | if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { 32 | throw new TypeError("Illegal invocation"); 33 | } 34 | } 35 | 36 | /** 37 | * Call a callback function immediately. 38 | */ 39 | function setImmediate(callback, ...args) { 40 | const asyncContext = getAsyncContext(); 41 | return core.queueImmediate(() => { 42 | const oldContext = getAsyncContext(); 43 | try { 44 | setAsyncContext(asyncContext); 45 | return ReflectApply(callback, globalThis, args); 46 | } finally { 47 | setAsyncContext(oldContext); 48 | } 49 | }); 50 | } 51 | 52 | /** 53 | * Call a callback function after a delay. 54 | */ 55 | function setTimeout(callback, timeout = 0, ...args) { 56 | checkThis(this); 57 | // If callback is a string, replace it with a function that evals the string on every timeout 58 | if (typeof callback !== "function") { 59 | const unboundCallback = webidl.converters.DOMString(callback); 60 | callback = () => indirectEval(unboundCallback); 61 | } 62 | const unboundCallback = callback; 63 | const asyncContext = getAsyncContext(); 64 | callback = () => { 65 | const oldContext = getAsyncContext(); 66 | try { 67 | setAsyncContext(asyncContext); 68 | ReflectApply(unboundCallback, globalThis, args); 69 | } finally { 70 | setAsyncContext(oldContext); 71 | } 72 | }; 73 | timeout = webidl.converters.long(timeout); 74 | return core.queueUserTimer( 75 | core.getTimerDepth() + 1, 76 | false, 77 | timeout, 78 | callback, 79 | ); 80 | } 81 | 82 | /** 83 | * Call a callback function after a delay. 84 | */ 85 | function setInterval(callback, timeout = 0, ...args) { 86 | checkThis(this); 87 | if (typeof callback !== "function") { 88 | const unboundCallback = webidl.converters.DOMString(callback); 89 | callback = () => indirectEval(unboundCallback); 90 | } 91 | const unboundCallback = callback; 92 | const asyncContext = getAsyncContext(); 93 | callback = () => { 94 | const oldContext = getAsyncContext(asyncContext); 95 | try { 96 | setAsyncContext(asyncContext); 97 | ReflectApply(unboundCallback, globalThis, args); 98 | } finally { 99 | setAsyncContext(oldContext); 100 | } 101 | }; 102 | timeout = webidl.converters.long(timeout); 103 | return core.queueUserTimer( 104 | core.getTimerDepth() + 1, 105 | true, 106 | timeout, 107 | callback, 108 | ); 109 | } 110 | 111 | /** 112 | * Clear a timeout or interval. 113 | */ 114 | function clearTimeout(id = 0) { 115 | checkThis(this); 116 | id = webidl.converters.long(id); 117 | core.cancelTimer(id); 118 | } 119 | 120 | /** 121 | * Clear a timeout or interval. 122 | */ 123 | function clearInterval(id = 0) { 124 | checkThis(this); 125 | id = webidl.converters.long(id); 126 | core.cancelTimer(id); 127 | } 128 | 129 | /** 130 | * Mark a timer as not blocking event loop exit. 131 | */ 132 | function unrefTimer(id) { 133 | core.unrefTimer(id); 134 | } 135 | 136 | /** 137 | * Mark a timer as blocking event loop exit. 138 | */ 139 | function refTimer(id) { 140 | core.refTimer(id); 141 | } 142 | 143 | // Defer to avoid starving the event loop. Not using queueMicrotask() 144 | // for that reason: it lets promises make forward progress but can 145 | // still starve other parts of the event loop. 146 | function defer(go) { 147 | PromisePrototypeThen(op_defer(), () => go()); 148 | } 149 | 150 | export { 151 | clearInterval, 152 | clearTimeout, 153 | defer, 154 | opNow, 155 | refTimer, 156 | setImmediate, 157 | setInterval, 158 | setTimeout, 159 | unrefTimer, 160 | }; -------------------------------------------------------------------------------- /src/ext/web_stub/05_base64.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | import * as webidl from "ext:deno_webidl/00_webidl.js"; 3 | import { op_base64_atob, op_base64_btoa } from "ext:core/ops"; 4 | import { primordials } from "ext:core/mod.js"; 5 | const { 6 | ObjectPrototypeIsPrototypeOf, 7 | TypeErrorPrototype, 8 | } = primordials; 9 | 10 | /** 11 | * @param {string} data 12 | * @returns {string} 13 | */ 14 | function atob(data) { 15 | const prefix = "Failed to execute 'atob'"; 16 | webidl.requiredArguments(arguments.length, 1, prefix); 17 | data = webidl.converters.DOMString(data, prefix, "Argument 1"); 18 | try { 19 | return op_base64_atob(data); 20 | } catch (e) { 21 | if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { 22 | throw new DOMException( 23 | "Failed to decode base64: invalid character", 24 | "InvalidCharacterError", 25 | ); 26 | } 27 | throw e; 28 | } 29 | } 30 | 31 | /** 32 | * @param {string} data 33 | * @returns {string} 34 | */ 35 | function btoa(data) { 36 | const prefix = "Failed to execute 'btoa'"; 37 | webidl.requiredArguments(arguments.length, 1, prefix); 38 | data = webidl.converters.DOMString(data, prefix, "Argument 1"); 39 | try { 40 | return op_base64_btoa(data); 41 | } catch (e) { 42 | if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { 43 | throw new DOMException( 44 | "The string to be encoded contains characters outside of the Latin1 range.", 45 | "InvalidCharacterError", 46 | ); 47 | } 48 | throw e; 49 | } 50 | } 51 | 52 | export { 53 | atob, btoa 54 | } -------------------------------------------------------------------------------- /src/ext/web_stub/encoding.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | use deno_core::{op2, v8, ByteString, ToJsBuffer}; 3 | 4 | #[derive(Debug, thiserror::Error, deno_error::JsError)] 5 | #[allow(dead_code)] 6 | pub enum WebError { 7 | #[class(generic)] 8 | #[error("Failed to decode base64")] 9 | Base64Decode, 10 | #[class(generic)] 11 | #[error("The encoding label provided ('{0}') is invalid.")] 12 | InvalidEncodingLabel(String), 13 | #[class(generic)] 14 | #[error("buffer exceeds maximum length")] 15 | BufferTooLong, 16 | #[class(generic)] 17 | #[error("Value too large to decode")] 18 | ValueTooLarge, 19 | #[class(generic)] 20 | #[error("Provided buffer too small")] 21 | BufferTooSmall, 22 | #[class(generic)] 23 | #[error("The encoded data is not valid")] 24 | DataInvalid, 25 | #[class(generic)] 26 | #[error(transparent)] 27 | DataError(#[from] v8::DataError), 28 | } 29 | 30 | #[op2] 31 | #[serde] 32 | pub fn op_base64_decode(#[string] input: String) -> Result { 33 | let mut s = input.into_bytes(); 34 | let decoded_len = forgiving_base64_decode_inplace(&mut s)?; 35 | s.truncate(decoded_len); 36 | Ok(s.into()) 37 | } 38 | 39 | #[op2] 40 | #[serde] 41 | pub fn op_base64_atob(#[serde] mut s: ByteString) -> Result { 42 | let decoded_len = forgiving_base64_decode_inplace(&mut s)?; 43 | s.truncate(decoded_len); 44 | Ok(s) 45 | } 46 | 47 | #[op2] 48 | #[string] 49 | pub fn op_base64_encode(#[buffer] s: &[u8]) -> String { 50 | forgiving_base64_encode(s) 51 | } 52 | 53 | #[op2] 54 | #[string] 55 | pub fn op_base64_btoa(#[serde] s: ByteString) -> String { 56 | forgiving_base64_encode(s.as_ref()) 57 | } 58 | 59 | /// See 60 | #[inline] 61 | fn forgiving_base64_decode_inplace(input: &mut [u8]) -> Result { 62 | let decoded = 63 | base64_simd::forgiving_decode_inplace(input).map_err(|_| WebError::Base64Decode)?; 64 | Ok(decoded.len()) 65 | } 66 | 67 | /// See 68 | #[inline] 69 | fn forgiving_base64_encode(s: &[u8]) -> String { 70 | base64_simd::STANDARD.encode_to_string(s) 71 | } 72 | -------------------------------------------------------------------------------- /src/ext/web_stub/init_stub.js: -------------------------------------------------------------------------------- 1 | import * as DOMException from 'ext:deno_web/01_dom_exception.js'; 2 | import * as timers from 'ext:deno_web/02_timers.js'; 3 | import * as base64 from 'ext:deno_web/05_base64.js'; 4 | 5 | import { applyToGlobal, nonEnumerable, writeable } from 'ext:rustyscript/rustyscript.js'; 6 | applyToGlobal({ 7 | DOMException: nonEnumerable(DOMException), 8 | 9 | setImmediate: writeable(timers.setImmediate), 10 | clearInterval: writeable(timers.clearInterval), 11 | clearTimeout: writeable(timers.clearTimeout), 12 | setInterval: writeable(timers.setInterval), 13 | setTimeout: writeable(timers.setTimeout), 14 | refTimer: writeable(timers.refTimer), 15 | unrefTimer: writeable(timers.unrefTimer), 16 | 17 | atob: writeable(base64.atob), 18 | btoa: writeable(base64.btoa), 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /src/ext/web_stub/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module is a stub for the `deno_web` extension. 2 | //! It is used when the `web` feature is disabled. 3 | //! 4 | //! It provides a minimal set of APIs that are required for a few other extensions. 5 | use super::ExtensionTrait; 6 | use deno_core::{extension, Extension}; 7 | 8 | mod encoding; 9 | mod timers; 10 | 11 | extension!( 12 | deno_web, 13 | ops = [ 14 | timers::op_now, timers::op_defer, 15 | encoding::op_base64_decode, encoding::op_base64_atob, encoding::op_base64_encode, encoding::op_base64_btoa, 16 | ], 17 | esm_entry_point = "ext:deno_web/init_stub.js", 18 | esm = [ dir "src/ext/web_stub", "init_stub.js", "01_dom_exception.js", "02_timers.js", "05_base64.js" ], 19 | ); 20 | impl ExtensionTrait<()> for deno_web { 21 | fn init((): ()) -> Extension { 22 | deno_web::init_ops_and_esm() 23 | } 24 | } 25 | 26 | pub fn extensions(is_snapshot: bool) -> Vec { 27 | vec![deno_web::build((), is_snapshot)] 28 | } 29 | -------------------------------------------------------------------------------- /src/ext/web_stub/timers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | //! This module helps deno implement timers and performance APIs. 3 | 4 | use deno_core::op2; 5 | use deno_core::OpState; 6 | use std::time::Instant; 7 | 8 | pub type StartTime = Instant; 9 | 10 | // Returns a milliseconds and nanoseconds subsec 11 | // since the start time of the deno runtime. 12 | // If the High precision flag is not set, the 13 | // nanoseconds are rounded on 2ms. 14 | #[op2(fast)] 15 | pub fn op_now(state: &mut OpState, #[buffer] buf: &mut [u8]) { 16 | let start_time = state.borrow::(); 17 | let elapsed = start_time.elapsed(); 18 | let seconds = elapsed.as_secs(); 19 | let mut subsec_nanos = elapsed.subsec_nanos(); 20 | 21 | let reduced_time_precision = 2_000_000; // 2ms in nanoseconds 22 | subsec_nanos -= subsec_nanos % reduced_time_precision; 23 | if buf.len() < 8 { 24 | return; 25 | } 26 | let buf: &mut [u32] = 27 | // SAFETY: buffer is at least 8 bytes long. 28 | unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), 2) }; 29 | buf[0] = u32::try_from(seconds).unwrap_or_default(); 30 | buf[1] = subsec_nanos; 31 | } 32 | 33 | #[allow(clippy::unused_async)] 34 | #[op2(async(lazy), fast)] 35 | pub async fn op_defer() {} 36 | -------------------------------------------------------------------------------- /src/ext/webgpu/init_webgpu.js: -------------------------------------------------------------------------------- 1 | import * as init from 'ext:deno_webgpu/00_init.js'; 2 | //import * as webgpu from 'ext:deno_webgpu/01_webgpu.js'; 3 | import * as webgpuSurface from 'ext:deno_webgpu/02_surface.js'; 4 | 5 | globalThis.Deno.UnsafeWindowSurface = webgpuSurface.UnsafeWindowSurface; 6 | 7 | 8 | 9 | /* 10 | import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 11 | applyToGlobal({ 12 | GPU: nonEnumerable((webgpu) => webgpu.GPU, loadWebGPU), 13 | GPUAdapter: nonEnumerable( 14 | (webgpu) => webgpu.GPUAdapter, 15 | loadWebGPU, 16 | ), 17 | GPUAdapterInfo: nonEnumerable( 18 | (webgpu) => webgpu.GPUAdapterInfo, 19 | loadWebGPU, 20 | ), 21 | GPUBuffer: nonEnumerable( 22 | (webgpu) => webgpu.GPUBuffer, 23 | loadWebGPU, 24 | ), 25 | GPUBufferUsage: nonEnumerable( 26 | (webgpu) => webgpu.GPUBufferUsage, 27 | loadWebGPU, 28 | ), 29 | GPUCanvasContext: nonEnumerable(webgpuSurface.GPUCanvasContext), 30 | GPUColorWrite: nonEnumerable( 31 | (webgpu) => webgpu.GPUColorWrite, 32 | loadWebGPU, 33 | ), 34 | GPUCommandBuffer: nonEnumerable( 35 | (webgpu) => webgpu.GPUCommandBuffer, 36 | loadWebGPU, 37 | ), 38 | GPUCommandEncoder: nonEnumerable( 39 | (webgpu) => webgpu.GPUCommandEncoder, 40 | loadWebGPU, 41 | ), 42 | GPUComputePassEncoder: nonEnumerable( 43 | (webgpu) => webgpu.GPUComputePassEncoder, 44 | loadWebGPU, 45 | ), 46 | GPUComputePipeline: nonEnumerable( 47 | (webgpu) => webgpu.GPUComputePipeline, 48 | loadWebGPU, 49 | ), 50 | GPUDevice: nonEnumerable( 51 | (webgpu) => webgpu.GPUDevice, 52 | loadWebGPU, 53 | ), 54 | GPUDeviceLostInfo: nonEnumerable( 55 | (webgpu) => webgpu.GPUDeviceLostInfo, 56 | loadWebGPU, 57 | ), 58 | GPUError: nonEnumerable( 59 | (webgpu) => webgpu.GPUError, 60 | loadWebGPU, 61 | ), 62 | GPUBindGroup: nonEnumerable( 63 | (webgpu) => webgpu.GPUBindGroup, 64 | loadWebGPU, 65 | ), 66 | GPUBindGroupLayout: nonEnumerable( 67 | (webgpu) => webgpu.GPUBindGroupLayout, 68 | loadWebGPU, 69 | ), 70 | GPUInternalError: nonEnumerable( 71 | (webgpu) => webgpu.GPUInternalError, 72 | loadWebGPU, 73 | ), 74 | GPUPipelineError: nonEnumerable( 75 | (webgpu) => webgpu.GPUPipelineError, 76 | loadWebGPU, 77 | ), 78 | GPUUncapturedErrorEvent: nonEnumerable( 79 | (webgpu) => webgpu.GPUUncapturedErrorEvent, 80 | loadWebGPU, 81 | ), 82 | GPUPipelineLayout: nonEnumerable( 83 | (webgpu) => webgpu.GPUPipelineLayout, 84 | loadWebGPU, 85 | ), 86 | GPUQueue: nonEnumerable( 87 | (webgpu) => webgpu.GPUQueue, 88 | loadWebGPU, 89 | ), 90 | GPUQuerySet: nonEnumerable( 91 | (webgpu) => webgpu.GPUQuerySet, 92 | loadWebGPU, 93 | ), 94 | GPUMapMode: nonEnumerable( 95 | (webgpu) => webgpu.GPUMapMode, 96 | loadWebGPU, 97 | ), 98 | GPUOutOfMemoryError: nonEnumerable( 99 | (webgpu) => webgpu.GPUOutOfMemoryError, 100 | loadWebGPU, 101 | ), 102 | GPURenderBundle: nonEnumerable( 103 | (webgpu) => webgpu.GPURenderBundle, 104 | loadWebGPU, 105 | ), 106 | GPURenderBundleEncoder: nonEnumerable( 107 | (webgpu) => webgpu.GPURenderBundleEncoder, 108 | loadWebGPU, 109 | ), 110 | GPURenderPassEncoder: nonEnumerable( 111 | (webgpu) => webgpu.GPURenderPassEncoder, 112 | loadWebGPU, 113 | ), 114 | GPURenderPipeline: nonEnumerable( 115 | (webgpu) => webgpu.GPURenderPipeline, 116 | loadWebGPU, 117 | ), 118 | GPUSampler: nonEnumerable( 119 | (webgpu) => webgpu.GPUSampler, 120 | loadWebGPU, 121 | ), 122 | GPUShaderModule: nonEnumerable( 123 | (webgpu) => webgpu.GPUShaderModule, 124 | loadWebGPU, 125 | ), 126 | GPUShaderStage: nonEnumerable( 127 | (webgpu) => webgpu.GPUShaderStage, 128 | loadWebGPU, 129 | ), 130 | GPUSupportedFeatures: nonEnumerable( 131 | (webgpu) => webgpu.GPUSupportedFeatures, 132 | loadWebGPU, 133 | ), 134 | GPUSupportedLimits: nonEnumerable( 135 | (webgpu) => webgpu.GPUSupportedLimits, 136 | loadWebGPU, 137 | ), 138 | GPUTexture: nonEnumerable( 139 | (webgpu) => webgpu.GPUTexture, 140 | loadWebGPU, 141 | ), 142 | GPUTextureView: nonEnumerable( 143 | (webgpu) => webgpu.GPUTextureView, 144 | loadWebGPU, 145 | ), 146 | GPUTextureUsage: nonEnumerable( 147 | (webgpu) => webgpu.GPUTextureUsage, 148 | loadWebGPU, 149 | ), 150 | GPUValidationError: nonEnumerable( 151 | (webgpu) => webgpu.GPUValidationError, 152 | loadWebGPU, 153 | ), 154 | })*/ -------------------------------------------------------------------------------- /src/ext/webgpu/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | extension!( 5 | init_webgpu, 6 | deps = [rustyscript], 7 | esm_entry_point = "ext:init_webgpu/init_webgpu.js", 8 | esm = [ dir "src/ext/webgpu", "init_webgpu.js" ], 9 | ); 10 | impl ExtensionTrait<()> for init_webgpu { 11 | fn init((): ()) -> Extension { 12 | init_webgpu::init_ops_and_esm() 13 | } 14 | } 15 | impl ExtensionTrait<()> for deno_webgpu::deno_webgpu { 16 | fn init((): ()) -> Extension { 17 | deno_webgpu::deno_webgpu::init_ops_and_esm() 18 | } 19 | } 20 | 21 | pub fn extensions(is_snapshot: bool) -> Vec { 22 | vec![ 23 | deno_webgpu::deno_webgpu::build((), is_snapshot), 24 | init_webgpu::build((), is_snapshot), 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/ext/webidl/init_webidl.js: -------------------------------------------------------------------------------- 1 | import * as webidl from 'ext:deno_webidl/00_webidl.js'; 2 | 3 | import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 4 | applyToGlobal({ 5 | // Branding as a WebIDL object 6 | [webidl.brand]: nonEnumerable(webidl.brand), 7 | }); -------------------------------------------------------------------------------- /src/ext/webidl/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | 4 | extension!( 5 | init_webidl, 6 | deps = [rustyscript], 7 | esm_entry_point = "ext:init_webidl/init_webidl.js", 8 | esm = [ dir "src/ext/webidl", "init_webidl.js" ], 9 | ); 10 | impl ExtensionTrait<()> for init_webidl { 11 | fn init((): ()) -> Extension { 12 | init_webidl::init_ops_and_esm() 13 | } 14 | } 15 | impl ExtensionTrait<()> for deno_webidl::deno_webidl { 16 | fn init((): ()) -> Extension { 17 | deno_webidl::deno_webidl::init_ops_and_esm() 18 | } 19 | } 20 | 21 | pub fn extensions(is_snapshot: bool) -> Vec { 22 | vec![ 23 | deno_webidl::deno_webidl::build((), is_snapshot), 24 | init_webidl::build((), is_snapshot), 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/ext/websocket/init_websocket.js: -------------------------------------------------------------------------------- 1 | import * as websocket from "ext:deno_websocket/01_websocket.js"; 2 | import * as websocketStream from "ext:deno_websocket/02_websocketstream.js"; 3 | 4 | import { applyToGlobal, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 5 | 6 | applyToGlobal({ 7 | WebSocket: nonEnumerable(websocket.WebSocket), 8 | WebSocketStream: nonEnumerable(websocketStream.WebSocketStream) 9 | }); -------------------------------------------------------------------------------- /src/ext/websocket/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{web::PermissionsContainer, web::WebOptions, ExtensionTrait}; 2 | use deno_core::{extension, url::Url, Extension}; 3 | use deno_permissions::PermissionCheckError; 4 | 5 | impl deno_websocket::WebSocketPermissions for PermissionsContainer { 6 | fn check_net_url(&mut self, url: &Url, api_name: &str) -> Result<(), PermissionCheckError> { 7 | self.0.check_url(url, api_name)?; 8 | Ok(()) 9 | } 10 | } 11 | 12 | extension!( 13 | init_websocket, 14 | deps = [rustyscript], 15 | esm_entry_point = "ext:init_websocket/init_websocket.js", 16 | esm = [ dir "src/ext/websocket", "init_websocket.js" ], 17 | ); 18 | impl ExtensionTrait<()> for init_websocket { 19 | fn init((): ()) -> Extension { 20 | init_websocket::init_ops_and_esm() 21 | } 22 | } 23 | impl ExtensionTrait for deno_websocket::deno_websocket { 24 | fn init(options: WebOptions) -> Extension { 25 | deno_websocket::deno_websocket::init_ops_and_esm::( 26 | options.user_agent, 27 | options.root_cert_store_provider, 28 | options.unsafely_ignore_certificate_errors, 29 | ) 30 | } 31 | } 32 | 33 | pub fn extensions(options: WebOptions, is_snapshot: bool) -> Vec { 34 | vec![ 35 | deno_websocket::deno_websocket::build(options, is_snapshot), 36 | init_websocket::build((), is_snapshot), 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/ext/webstorage/init_webstorage.js: -------------------------------------------------------------------------------- 1 | import * as webStorage from "ext:deno_webstorage/01_webstorage.js"; 2 | 3 | import { applyToGlobal, getterOnly, nonEnumerable } from 'ext:rustyscript/rustyscript.js'; 4 | applyToGlobal({ 5 | Storage: nonEnumerable(webStorage.Storage), 6 | sessionStorage: getterOnly(webStorage.sessionStorage), 7 | localStorage: getterOnly(webStorage.localStorage), 8 | }); -------------------------------------------------------------------------------- /src/ext/webstorage/mod.rs: -------------------------------------------------------------------------------- 1 | use super::ExtensionTrait; 2 | use deno_core::{extension, Extension}; 3 | use std::path::PathBuf; 4 | 5 | extension!( 6 | init_webstorage, 7 | deps = [rustyscript], 8 | esm_entry_point = "ext:init_webstorage/init_webstorage.js", 9 | esm = [ dir "src/ext/webstorage", "init_webstorage.js" ], 10 | ); 11 | impl ExtensionTrait<()> for init_webstorage { 12 | fn init((): ()) -> Extension { 13 | init_webstorage::init_ops_and_esm() 14 | } 15 | } 16 | impl ExtensionTrait> for deno_webstorage::deno_webstorage { 17 | fn init(origin_storage_dir: Option) -> Extension { 18 | deno_webstorage::deno_webstorage::init_ops_and_esm(origin_storage_dir) 19 | } 20 | } 21 | 22 | pub fn extensions(origin_storage_dir: Option, is_snapshot: bool) -> Vec { 23 | vec![ 24 | deno_webstorage::deno_webstorage::build(origin_storage_dir, is_snapshot), 25 | init_webstorage::build((), is_snapshot), 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/js_value/function.rs: -------------------------------------------------------------------------------- 1 | use super::V8Value; 2 | use deno_core::v8::{self, HandleScope}; 3 | use serde::Deserialize; 4 | 5 | /// A Deserializable javascript function, that can be stored and used later 6 | /// Must live as long as the runtime it was birthed from 7 | #[derive(Eq, Hash, PartialEq, Debug, Clone)] 8 | pub struct Function(V8Value); 9 | impl_v8!(Function, FunctionTypeChecker); 10 | impl_checker!(FunctionTypeChecker, Function, is_function, |e| { 11 | crate::Error::ValueNotCallable(e) 12 | }); 13 | 14 | impl Function { 15 | pub(crate) fn as_global(&self, scope: &mut HandleScope<'_>) -> v8::Global { 16 | self.0.as_global(scope) 17 | } 18 | 19 | /// Returns true if the function is async 20 | #[must_use] 21 | pub fn is_async(&self) -> bool { 22 | // Safe because we aren't applying this to an isolate 23 | let unsafe_f = unsafe { v8::Handle::get_unchecked(&self.0 .0) }; 24 | unsafe_f.is_async_function() 25 | } 26 | 27 | /// Calls this function. See [`crate::Runtime::call_stored_function`] 28 | /// Blocks until: 29 | /// - The event loop is resolved, and 30 | /// - If the value is a promise, the promise is resolved 31 | /// 32 | /// # Errors 33 | /// Will return an error if the function cannot be called, if the function returns an error 34 | /// Or if the function returns a value that cannot be deserialized into the given type 35 | pub fn call( 36 | &self, 37 | runtime: &mut crate::Runtime, 38 | module_context: Option<&crate::ModuleHandle>, 39 | args: &impl serde::ser::Serialize, 40 | ) -> Result 41 | where 42 | T: serde::de::DeserializeOwned, 43 | { 44 | runtime.call_stored_function(module_context, self, args) 45 | } 46 | 47 | /// Calls this function. See [`crate::Runtime::call_stored_function_async`] 48 | /// Returns a future that resolves when: 49 | /// - The event loop is resolved, and 50 | /// - If the value is a promise, the promise is resolved 51 | /// 52 | /// # Errors 53 | /// Will return an error if the function cannot be called, if the function returns an error 54 | /// Or if the function returns a value that cannot be deserialized into the given type 55 | pub async fn call_async( 56 | &self, 57 | runtime: &mut crate::Runtime, 58 | module_context: Option<&crate::ModuleHandle>, 59 | args: &impl serde::ser::Serialize, 60 | ) -> Result 61 | where 62 | T: serde::de::DeserializeOwned, 63 | { 64 | runtime 65 | .call_stored_function_async(module_context, self, args) 66 | .await 67 | } 68 | 69 | /// Calls this function. See [`crate::Runtime::call_stored_function_immediate`] 70 | /// Does not wait for the event loop to resolve, or attempt to resolve promises 71 | /// 72 | /// # Errors 73 | /// Will return an error if the function cannot be called, if the function returns an error 74 | /// Or if the function returns a value that cannot be deserialized into the given type 75 | pub fn call_immediate( 76 | &self, 77 | runtime: &mut crate::Runtime, 78 | module_context: Option<&crate::ModuleHandle>, 79 | args: &impl serde::ser::Serialize, 80 | ) -> Result 81 | where 82 | T: serde::de::DeserializeOwned, 83 | { 84 | runtime.call_stored_function_immediate(module_context, self, args) 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod test { 90 | use super::*; 91 | use crate::{js_value::Promise, json_args, Module, Runtime, RuntimeOptions}; 92 | 93 | #[test] 94 | fn test_function() { 95 | let module = Module::new( 96 | "test.js", 97 | " 98 | export const f = () => 42; 99 | export const f2 = async () => 42; 100 | ", 101 | ); 102 | 103 | let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); 104 | let handle = runtime.load_module(&module).unwrap(); 105 | 106 | let f: Function = runtime.get_value(Some(&handle), "f").unwrap(); 107 | let value: usize = f.call(&mut runtime, Some(&handle), &json_args!()).unwrap(); 108 | assert_eq!(value, 42); 109 | 110 | let f2: Function = runtime.get_value(Some(&handle), "f2").unwrap(); 111 | let value: Promise = f2 112 | .call_immediate(&mut runtime, Some(&handle), &json_args!()) 113 | .unwrap(); 114 | let value = value.into_value(&mut runtime).unwrap(); 115 | assert_eq!(value, 42); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/js_value/map.rs: -------------------------------------------------------------------------------- 1 | use super::V8Value; 2 | use deno_core::v8::{self, GetPropertyNamesArgs, HandleScope}; 3 | use serde::Deserialize; 4 | 5 | /// A Deserializable javascript object, that can be stored and used later 6 | /// Must live as long as the runtime it was birthed from 7 | /// 8 | /// Allows read-only access properties of the object, and convert it to a hashmap 9 | /// (skipping any keys that are not valid UTF-8) 10 | /// 11 | /// [`Map::get`] returns a [`crate::js_value::Value`] which can be converted to any rust type, including promises or functions 12 | #[derive(Eq, Hash, PartialEq, Debug, Clone)] 13 | pub struct Map(V8Value); 14 | impl_v8!(Map, ObjectTypeChecker); 15 | impl_checker!(ObjectTypeChecker, Object, is_object, |e| { 16 | crate::Error::JsonDecode(format!("Expected an object, found `{e}`")) 17 | }); 18 | 19 | impl Map { 20 | /// Gets a value from the map 21 | /// Warning: If a key is not valid UTF-8, the value may be inaccessible 22 | pub fn get(&self, key: &str, runtime: &mut crate::Runtime) -> Option { 23 | let mut scope = runtime.deno_runtime().handle_scope(); 24 | self.get_property_by_name(&mut scope, key) 25 | } 26 | 27 | /// Converts the map to a hashmap 28 | /// Skips any keys that are not valid UTF-8 29 | pub fn to_hashmap( 30 | &self, 31 | runtime: &mut crate::Runtime, 32 | ) -> std::collections::HashMap { 33 | let mut scope = runtime.deno_runtime().handle_scope(); 34 | self.to_rust_hashmap(&mut scope) 35 | } 36 | 37 | /// Returns the keys of the map 38 | /// Warning: If a key is not valid UTF-8, the value may be inaccessible 39 | pub fn keys(&self, runtime: &mut crate::Runtime) -> Vec { 40 | let mut scope = runtime.deno_runtime().handle_scope(); 41 | self.get_string_keys(&mut scope) 42 | } 43 | 44 | /// Returns the number of keys in the map 45 | /// Skips any keys that are not valid UTF-8 46 | pub fn len(&self, runtime: &mut crate::Runtime) -> usize { 47 | let mut scope = runtime.deno_runtime().handle_scope(); 48 | self.get_string_keys(&mut scope).len() 49 | } 50 | 51 | pub(crate) fn to_rust_hashmap( 52 | &self, 53 | scope: &mut HandleScope, 54 | ) -> std::collections::HashMap { 55 | let keys = self.get_string_keys(scope); 56 | let mut map = std::collections::HashMap::new(); 57 | 58 | for name in keys { 59 | match self.get_property_by_name(scope, &name) { 60 | Some(value) => map.insert(name, value), 61 | None => None, 62 | }; 63 | } 64 | 65 | map 66 | } 67 | 68 | pub(crate) fn get_property_by_name( 69 | &self, 70 | scope: &mut HandleScope, 71 | name: &str, 72 | ) -> Option { 73 | let local = self.0.as_local(scope); 74 | let key = v8::String::new(scope, name).unwrap(); 75 | let value = local.get(scope, key.into())?; 76 | 77 | let value = v8::Global::new(scope, value); 78 | Some(crate::js_value::Value::from_v8(value)) 79 | } 80 | 81 | pub(crate) fn get_string_keys(&self, scope: &mut HandleScope) -> Vec { 82 | let local = self.0.as_local(scope); 83 | let mut keys = vec![]; 84 | 85 | let v8_keys = local.get_own_property_names( 86 | scope, 87 | GetPropertyNamesArgs { 88 | mode: v8::KeyCollectionMode::OwnOnly, 89 | property_filter: v8::PropertyFilter::ALL_PROPERTIES, 90 | index_filter: v8::IndexFilter::IncludeIndices, 91 | key_conversion: v8::KeyConversionMode::ConvertToString, 92 | }, 93 | ); 94 | 95 | if let Some(v8_keys) = v8_keys { 96 | for i in 0..v8_keys.length() { 97 | let key = v8_keys.get_index(scope, i).unwrap(); 98 | let key = key.to_rust_string_lossy(scope); 99 | keys.push(key); 100 | } 101 | } 102 | 103 | keys 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod test { 109 | use super::*; 110 | use crate::{Module, Runtime, RuntimeOptions}; 111 | 112 | #[test] 113 | fn test_map() { 114 | let module = Module::new( 115 | "test.js", 116 | " 117 | export const m = { a: 1, b: 2, c: 3, 0: 4 }; 118 | ", 119 | ); 120 | 121 | let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); 122 | let handle = runtime.load_module(&module).unwrap(); 123 | 124 | let m: Map = runtime.get_value(Some(&handle), "m").expect("oops"); 125 | assert_eq!(m.len(&mut runtime), 4); 126 | 127 | let a = m.get("a", &mut runtime).unwrap(); 128 | let a: usize = a.try_into(&mut runtime).unwrap(); 129 | assert_eq!(a, 1); 130 | 131 | let zero = m.get("0", &mut runtime).unwrap(); 132 | let zero: usize = zero.try_into(&mut runtime).unwrap(); 133 | assert_eq!(zero, 4); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/js_value/promise.rs: -------------------------------------------------------------------------------- 1 | use super::V8Value; 2 | use crate::{async_bridge::AsyncBridgeExt, Error}; 3 | use deno_core::{ 4 | v8::{self, PromiseState}, 5 | PollEventLoopOptions, 6 | }; 7 | use serde::Deserialize; 8 | 9 | /// A Deserializable javascript promise, that can be stored and used later 10 | /// Must live as long as the runtime it was birthed from 11 | /// 12 | /// You can turn `Promise` into `Future` by calling `Promise::into_future` 13 | /// This allows you to export multiple concurrent promises without borrowing the runtime mutably 14 | #[derive(Eq, Hash, PartialEq, Debug, Clone)] 15 | pub struct Promise(V8Value, std::marker::PhantomData) 16 | where 17 | T: serde::de::DeserializeOwned; 18 | impl_v8!(Promise, PromiseTypeChecker); 19 | impl_checker!(PromiseTypeChecker, Promise, is_promise, |e| { 20 | crate::Error::JsonDecode(format!("Expected a promise, found `{e}`")) 21 | }); 22 | 23 | impl Promise 24 | where 25 | T: serde::de::DeserializeOwned, 26 | { 27 | pub(crate) async fn resolve( 28 | self, 29 | runtime: &mut deno_core::JsRuntime, 30 | ) -> Result { 31 | let future = runtime.resolve(self.0 .0); 32 | let result = runtime 33 | .with_event_loop_future(future, PollEventLoopOptions::default()) 34 | .await?; 35 | let mut scope = runtime.handle_scope(); 36 | let local = v8::Local::new(&mut scope, &result); 37 | Ok(deno_core::serde_v8::from_v8(&mut scope, local)?) 38 | } 39 | 40 | /// Returns a future that resolves the promise 41 | /// 42 | /// # Errors 43 | /// Will return an error if the promise cannot be resolved into the given type, 44 | /// or if a runtime error occurs 45 | pub async fn into_future(self, runtime: &mut crate::Runtime) -> Result { 46 | self.resolve(runtime.deno_runtime()).await 47 | } 48 | 49 | /// Blocks until the promise is resolved 50 | /// 51 | /// # Errors 52 | /// Will return an error if the promise cannot be resolved into the given type, 53 | /// or if a runtime error occurs 54 | pub fn into_value(self, runtime: &mut crate::Runtime) -> Result { 55 | runtime.block_on(move |runtime| async move { self.into_future(runtime).await }) 56 | } 57 | 58 | /// Checks if the promise is pending or already resolved 59 | pub fn is_pending(&self, runtime: &mut crate::Runtime) -> bool { 60 | let mut scope = runtime.deno_runtime().handle_scope(); 61 | let value = self.0.as_local(&mut scope); 62 | value.state() == v8::PromiseState::Pending 63 | } 64 | 65 | /// Polls the promise, returning `Poll::Pending` if the promise is still pending 66 | /// or `Poll::Ready(Ok(T))` if the promise is resolved 67 | /// or `Poll::Ready(Err(Error))` if the promise is rejected 68 | pub fn poll_promise(&self, runtime: &mut crate::Runtime) -> std::task::Poll> { 69 | let mut scope = runtime.deno_runtime().handle_scope(); 70 | let value = self.0.as_local(&mut scope); 71 | 72 | match value.state() { 73 | PromiseState::Pending => std::task::Poll::Pending, 74 | PromiseState::Rejected => { 75 | let error = value.result(&mut scope); 76 | let error = deno_core::error::JsError::from_v8_exception(&mut scope, error); 77 | std::task::Poll::Ready(Err(error.into())) 78 | } 79 | PromiseState::Fulfilled => { 80 | let result = value.result(&mut scope); 81 | match deno_core::serde_v8::from_v8::(&mut scope, result) { 82 | Ok(value) => std::task::Poll::Ready(Ok(value)), 83 | Err(e) => std::task::Poll::Ready(Err(e.into())), 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test { 92 | use super::*; 93 | use crate::{js_value::Function, json_args, Module, Runtime, RuntimeOptions}; 94 | 95 | #[test] 96 | fn test_promise() { 97 | let module = Module::new( 98 | "test.js", 99 | " 100 | export const f = () => new Promise((resolve) => resolve(42)); 101 | ", 102 | ); 103 | 104 | let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); 105 | let handle = runtime.load_module(&module).unwrap(); 106 | 107 | let f: Function = runtime.get_value(Some(&handle), "f").unwrap(); 108 | let value: Promise = f 109 | .call_immediate(&mut runtime, Some(&handle), &json_args!()) 110 | .unwrap(); 111 | let value = value.into_value(&mut runtime).unwrap(); 112 | assert_eq!(value, 42); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/js_value/string.rs: -------------------------------------------------------------------------------- 1 | use super::V8Value; 2 | use deno_core::v8::{self, HandleScope, WriteFlags}; 3 | use serde::Deserialize; 4 | 5 | /// A Deserializable javascript UTF-16 string, that can be stored and used later 6 | /// Must live as long as the runtime it was birthed from 7 | #[derive(Eq, Hash, PartialEq, Debug, Clone)] 8 | pub struct String(V8Value); 9 | impl_v8!(String, StringTypeChecker); 10 | impl_checker!(StringTypeChecker, String, is_string, |e| { 11 | crate::Error::JsonDecode(format!("Expected a string, found `{e}`")) 12 | }); 13 | 14 | impl String { 15 | /// Converts the string to a rust string 16 | /// Potentially lossy, if the string contains orphan UTF-16 surrogates 17 | pub fn to_string_lossy(&self, runtime: &mut crate::Runtime) -> std::string::String { 18 | let mut scope = runtime.deno_runtime().handle_scope(); 19 | self.to_rust_string_lossy(&mut scope) 20 | } 21 | 22 | /// Converts the string to a rust string 23 | /// If the string contains orphan UTF-16 surrogates, it may return None 24 | /// In that case, you can use `to_string_lossy` to get a lossy conversion 25 | pub fn to_string(&self, runtime: &mut crate::Runtime) -> Option { 26 | let bytes = self.to_utf8_bytes(runtime); 27 | std::string::String::from_utf8(bytes).ok() 28 | } 29 | 30 | /// Converts the string to a UTF-8 character buffer in the form of a `Vec` 31 | /// Excludes the null terminator 32 | pub fn to_utf8_bytes(&self, runtime: &mut crate::Runtime) -> Vec { 33 | let mut scope = runtime.deno_runtime().handle_scope(); 34 | self.to_utf8_buffer(&mut scope) 35 | } 36 | 37 | /// Converts the string to a UTF-16 character buffer in the form of a `Vec` 38 | /// Excludes the null terminator 39 | pub fn to_utf16_bytes(&self, runtime: &mut crate::Runtime) -> Vec { 40 | let mut scope = runtime.deno_runtime().handle_scope(); 41 | self.to_utf16_buffer(&mut scope) 42 | } 43 | 44 | pub(crate) fn to_rust_string_lossy(&self, scope: &mut HandleScope<'_>) -> std::string::String { 45 | let local = self.0.as_local(scope); 46 | local.to_rust_string_lossy(scope) 47 | } 48 | 49 | pub(crate) fn to_utf16_buffer(&self, scope: &mut HandleScope<'_>) -> Vec { 50 | let local = self.0.as_local(scope); 51 | let u16_len = local.length(); 52 | let mut buffer = vec![0; u16_len]; 53 | 54 | local.write_v2(scope, 0, &mut buffer, WriteFlags::empty()); 55 | buffer 56 | } 57 | 58 | pub(crate) fn to_utf8_buffer(&self, scope: &mut HandleScope<'_>) -> Vec { 59 | let local = self.0.as_local(scope); 60 | let u8_len = local.utf8_length(scope); 61 | let mut buffer = vec![0; u8_len]; 62 | 63 | local.write_utf8_v2(scope, &mut buffer, WriteFlags::empty()); 64 | buffer 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod test { 70 | use super::*; 71 | use crate::{Module, Runtime, RuntimeOptions}; 72 | 73 | #[test] 74 | fn test_string() { 75 | let module = Module::new( 76 | "test.js", 77 | " 78 | // Valid UTF-8 79 | export const good = 'Hello, World!'; 80 | 81 | // Invalid UTF-8, valid UTF-16 82 | export const bad = '\\ud83d\\ude00'; 83 | ", 84 | ); 85 | 86 | let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap(); 87 | let handle = runtime.load_module(&module).unwrap(); 88 | 89 | let f: String = runtime.get_value(Some(&handle), "good").unwrap(); 90 | let value = f.to_string_lossy(&mut runtime); 91 | assert_eq!(value, "Hello, World!"); 92 | 93 | let f: String = runtime.get_value(Some(&handle), "good").unwrap(); 94 | let value = f.to_string(&mut runtime).unwrap(); 95 | assert_eq!(value, "Hello, World!"); 96 | 97 | let f: String = runtime.get_value(Some(&handle), "bad").unwrap(); 98 | let value = f.to_utf16_bytes(&mut runtime); 99 | assert_eq!(value, vec![0xd83d, 0xde00]); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/module_handle.rs: -------------------------------------------------------------------------------- 1 | use deno_core::v8; 2 | use deno_core::ModuleId; 3 | 4 | use crate::Module; 5 | 6 | /// Represents a loaded instance of a module within a runtime 7 | #[derive(Clone, Debug, Eq, PartialEq, Default)] 8 | pub struct ModuleHandle { 9 | entrypoint: Option>, 10 | module_id: ModuleId, 11 | module: Module, 12 | } 13 | 14 | impl ModuleHandle { 15 | /// Create a new module instance 16 | pub(crate) fn new( 17 | module: &Module, 18 | module_id: ModuleId, 19 | entrypoint: Option>, 20 | ) -> Self { 21 | Self { 22 | module_id, 23 | entrypoint, 24 | module: module.clone(), 25 | } 26 | } 27 | 28 | /// Create a new module handle from raw parts 29 | /// 30 | /// # Safety 31 | /// This function is unsafe because it allows using potentially invalid `ModuleIds`. 32 | /// 33 | /// Use of an unloaded module ID will result in a panic. 34 | #[must_use] 35 | pub unsafe fn from_raw( 36 | module: &Module, 37 | module_id: ModuleId, 38 | entrypoint: Option>, 39 | ) -> Self { 40 | Self::new(module, module_id, entrypoint) 41 | } 42 | 43 | /// Return this module's contents 44 | #[must_use] 45 | pub fn module(&self) -> &Module { 46 | &self.module 47 | } 48 | 49 | /// Return this module's ID 50 | #[must_use] 51 | pub fn id(&self) -> ModuleId { 52 | self.module_id 53 | } 54 | 55 | /// Return this module's entrypoint 56 | #[must_use] 57 | pub fn entrypoint(&self) -> &Option> { 58 | &self.entrypoint 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/module_loader/cache_provider.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a trait for caching module data for the loader 2 | use deno_core::{ 3 | ModuleCodeBytes, ModuleSource, ModuleSourceCode, ModuleSpecifier, SourceCodeCacheInfo, 4 | }; 5 | 6 | /// A helper trait to clone a `ModuleSource` 7 | /// `deno_core::ModuleSource` does not implement Clone, so we need to implement it ourselves 8 | /// for our cache providers to work 9 | /// 10 | /// Todo: This is a temporary solution, we should submit a PR to `deno_core` to implement Clone for `ModuleSource` 11 | pub trait ClonableSource { 12 | /// Create a new copy of a `ModuleSource` 13 | fn clone(&self, specifier: &ModuleSpecifier) -> ModuleSource; 14 | } 15 | impl ClonableSource for ModuleSource { 16 | fn clone(&self, specifier: &ModuleSpecifier) -> ModuleSource { 17 | ModuleSource::new( 18 | self.module_type.clone(), 19 | match &self.code { 20 | ModuleSourceCode::String(s) => ModuleSourceCode::String(s.to_string().into()), 21 | ModuleSourceCode::Bytes(b) => { 22 | ModuleSourceCode::Bytes(ModuleCodeBytes::Boxed(b.to_vec().into())) 23 | } 24 | }, 25 | specifier, 26 | self.code_cache.as_ref().map(|c| SourceCodeCacheInfo { 27 | hash: c.hash, 28 | data: c.data.clone(), 29 | }), 30 | ) 31 | } 32 | } 33 | 34 | /// Module cache provider trait 35 | /// Implement this trait to provide a custom module cache for the loader 36 | /// The cache is used to store module data for later use, potentially saving time on re-fetching modules 37 | #[deprecated( 38 | since = "0.7.0", 39 | note = "This trait is being replaced by the `ImportProvider` trait, which provides more control over module resolution. See the `module_loader_cache` example for more information." 40 | )] 41 | pub trait ModuleCacheProvider { 42 | /// Apply a module to the cache 43 | fn set(&mut self, specifier: &ModuleSpecifier, source: ModuleSource); 44 | 45 | /// Get a module from the cache 46 | fn get(&self, specifier: &ModuleSpecifier) -> Option; 47 | } 48 | -------------------------------------------------------------------------------- /src/module_loader/import_provider.rs: -------------------------------------------------------------------------------- 1 | use deno_core::{error::ModuleLoaderError, ModuleSource, ModuleSpecifier, RequestedModuleType}; 2 | 3 | /// A trait that can be implemented to modify the behavior of the module loader 4 | /// Allows for custom schemes, caching, and more granular permissions 5 | #[allow(unused_variables)] 6 | pub trait ImportProvider { 7 | /// Resolve an import statement's specifier to a URL to later be imported 8 | /// This can be used to modify the URL, or to disallow certain imports 9 | /// 10 | /// The default behavior is to return None, which will fall back to the standard resolution behavior 11 | /// 12 | /// # Arguments 13 | /// - `specifier`: The module specifier to resolve, as an absolute URL 14 | /// - `referrer`: The URL of the module that is importing the specifier 15 | /// - `kind`: The kind of resolution being performed (e.g. main module, import, dynamic import) 16 | /// 17 | /// # Returns 18 | /// - Some(Ok(ModuleSpecifier)): The resolved module specifier that will be imported 19 | /// - Some(Err(Error)): An error that will be returned to the caller, denying the import 20 | /// - None: Fall back to the default resolution behavior 21 | fn resolve( 22 | &mut self, 23 | specifier: &ModuleSpecifier, 24 | referrer: &str, 25 | kind: deno_core::ResolutionKind, 26 | ) -> Option> { 27 | None 28 | } 29 | 30 | /// Retrieve a JavaScript/TypeScript module from a given URL and return it as a string. 31 | /// 32 | /// # Arguments 33 | /// - `specifier`: The module specifier to import, as an absolute URL 34 | /// - `referrer`: The URL of the module that is importing the specifier 35 | /// - `is_dyn_import`: Whether the import is a dynamic import or not 36 | /// - `requested_module_type`: The type of module being requested 37 | /// 38 | /// # Returns 39 | /// - Some(Ok(String)): The module source code as a string 40 | /// - Some(Err(Error)): An error that will be returned to the caller 41 | /// - None: Fall back to the default import behavior 42 | fn import( 43 | &mut self, 44 | specifier: &ModuleSpecifier, 45 | referrer: Option<&ModuleSpecifier>, 46 | is_dyn_import: bool, 47 | requested_module_type: RequestedModuleType, 48 | ) -> Option> { 49 | None 50 | } 51 | 52 | /// Apply an optional transform to the source code after it has been imported 53 | /// This can be used to modify the source code before it is executed 54 | /// Or to cache the source code for later use 55 | /// 56 | /// The default behavior is to return the source code unmodified 57 | /// 58 | /// # Arguments 59 | /// - `specifier`: The module specifier that was imported 60 | /// - `source`: The source code of the module 61 | /// 62 | /// # Returns 63 | /// - Ok(ModuleSource): The modified source code 64 | /// - Err(Error): An error that will be returned to the caller 65 | /// 66 | /// # Errors 67 | /// - Any error that occurs during post-processing 68 | fn post_process( 69 | &mut self, 70 | specifier: &ModuleSpecifier, 71 | source: ModuleSource, 72 | ) -> Result { 73 | Ok(source) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use deno_core::v8::{self, HandleScope}; 3 | use deno_core::ModuleSpecifier; 4 | use std::path::Path; 5 | 6 | /// Converts a string representing a relative or absolute path into a 7 | /// `ModuleSpecifier`. A relative path is considered relative to the passed 8 | /// `current_dir`. 9 | /// 10 | /// This is a patch for the str only `deno_core` provided version 11 | fn resolve_path( 12 | path_str: impl AsRef, 13 | current_dir: &Path, 14 | ) -> Result { 15 | let path = current_dir.join(path_str); 16 | let path = deno_core::normalize_path(path); 17 | deno_core::url::Url::from_file_path(&path).map_err(|()| { 18 | deno_core::ModuleResolutionError::InvalidUrl( 19 | deno_core::url::ParseError::RelativeUrlWithoutBase, 20 | ) 21 | }) 22 | } 23 | 24 | pub trait ToModuleSpecifier { 25 | fn to_module_specifier(&self, base: &Path) -> Result; 26 | } 27 | 28 | impl> ToModuleSpecifier for T { 29 | fn to_module_specifier(&self, base: &Path) -> Result { 30 | Ok(resolve_path(self, base)?) 31 | } 32 | } 33 | 34 | pub trait ToV8String { 35 | fn to_v8_string<'a>( 36 | &self, 37 | scope: &mut HandleScope<'a>, 38 | ) -> Result, Error>; 39 | } 40 | 41 | impl ToV8String for str { 42 | fn to_v8_string<'a>( 43 | &self, 44 | scope: &mut HandleScope<'a>, 45 | ) -> Result, Error> { 46 | v8::String::new(scope, self).ok_or(Error::V8Encoding(self.to_string())) 47 | } 48 | } 49 | 50 | pub trait ToDefinedValue { 51 | fn if_defined(&self) -> Option; 52 | } 53 | 54 | impl<'a> ToDefinedValue> for Option> { 55 | fn if_defined(&self) -> Option> { 56 | self.filter(|v| !v.is_undefined()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/transpiler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. 2 | //! This file transpiles TypeScript and JSX/TSX 3 | //! modules. 4 | //! 5 | //! It will only transpile, not typecheck (like Deno's `--no-check` flag). 6 | 7 | use deno_ast::MediaType; 8 | use deno_ast::ParseDiagnosticsError; 9 | use deno_ast::ParseParams; 10 | use deno_ast::SourceTextInfo; 11 | use deno_ast::TranspileError; 12 | use deno_core::FastString; 13 | use deno_core::ModuleSpecifier; 14 | use deno_core::SourceMapData; 15 | use deno_error::JsErrorBox; 16 | use std::borrow::Cow; 17 | use std::rc::Rc; 18 | 19 | pub type ModuleContents = (String, Option); 20 | 21 | fn should_transpile(media_type: MediaType) -> bool { 22 | matches!( 23 | media_type, 24 | MediaType::Jsx 25 | | MediaType::TypeScript 26 | | MediaType::Mts 27 | | MediaType::Cts 28 | | MediaType::Dts 29 | | MediaType::Dmts 30 | | MediaType::Dcts 31 | | MediaType::Tsx 32 | ) 33 | } 34 | 35 | /// 36 | /// Transpiles source code from TS to JS without typechecking 37 | pub fn transpile( 38 | module_specifier: &ModuleSpecifier, 39 | code: &str, 40 | ) -> Result { 41 | let mut media_type = MediaType::from_specifier(module_specifier); 42 | 43 | if media_type == MediaType::Unknown && module_specifier.as_str().contains("/node:") { 44 | media_type = MediaType::TypeScript; 45 | } 46 | 47 | let should_transpile = should_transpile(media_type); 48 | 49 | let code = if should_transpile { 50 | let sti = SourceTextInfo::from_string(code.to_string()); 51 | let text = sti.text(); 52 | let parsed = deno_ast::parse_module(ParseParams { 53 | specifier: module_specifier.clone(), 54 | text, 55 | media_type, 56 | capture_tokens: false, 57 | scope_analysis: false, 58 | maybe_syntax: None, 59 | }) 60 | .map_err(|e| TranspileError::ParseErrors(ParseDiagnosticsError(vec![e])))?; 61 | 62 | let transpile_options = deno_ast::TranspileOptions { 63 | ..Default::default() 64 | }; 65 | 66 | let transpile_mod_options = deno_ast::TranspileModuleOptions { 67 | ..Default::default() 68 | }; 69 | 70 | let emit_options = deno_ast::EmitOptions { 71 | remove_comments: false, 72 | source_map: deno_ast::SourceMapOption::Separate, 73 | inline_sources: false, 74 | ..Default::default() 75 | }; 76 | let res = parsed 77 | .transpile(&transpile_options, &transpile_mod_options, &emit_options)? 78 | .into_source(); 79 | 80 | let text = res.text; 81 | 82 | let source_map: Option = res.source_map.map(|sm| sm.into_bytes().into()); 83 | 84 | (text, source_map) 85 | } else { 86 | (code.to_string(), None) 87 | }; 88 | 89 | Ok(code) 90 | } 91 | 92 | /// 93 | /// Transpile an extension 94 | #[allow(clippy::type_complexity)] 95 | pub fn transpile_extension( 96 | specifier: &ModuleSpecifier, 97 | code: &str, 98 | ) -> Result<(FastString, Option>), JsErrorBox> { 99 | let (code, source_map) = transpile(specifier, code).map_err(JsErrorBox::from_err)?; 100 | let code = FastString::from(code); 101 | Ok((code, source_map)) 102 | } 103 | 104 | pub type ExtensionTranspiler = Rc< 105 | dyn Fn(FastString, FastString) -> Result<(FastString, Option>), JsErrorBox>, 106 | >; 107 | pub type ExtensionTranspilation = (FastString, Option>); 108 | --------------------------------------------------------------------------------