Skip to main content

javy/
config.rs

1use std::{
2    fmt::Debug,
3    io::{self, Write},
4};
5
6use anyhow::{Result, bail};
7use bitflags::bitflags;
8
9bitflags! {
10    /// Flags to represent available JavaScript features.
11    #[derive(Debug)]
12    pub(crate) struct JSIntrinsics: u32  {
13        const DATE = 1;
14        const EVAL = 1 << 1;
15        const REGEXP_COMPILER = 1 << 2;
16        const REGEXP = 1 << 3;
17        const JSON = 1 << 4;
18        const PROXY = 1 << 5;
19        const MAP_SET = 1 << 6;
20        const TYPED_ARRAY  = 1 << 7;
21        const PROMISE  = 1 << 8;
22        const BIG_INT = 1 << 9;
23        // Removed 10 and 11 representing BIG_FLOAT and BIG_DECIMAL.
24        const OPERATORS = 1 << 12;
25        const BIGNUM_EXTENSION = 1 << 13;
26        const TEXT_ENCODING = 1 << 14;
27        // Removed 15 representing STRING_NORMALIZE.
28        const WEAK_REF = 1 << 16;
29        const PERFORMANCE = 1 << 17;
30    }
31}
32
33bitflags! {
34    /// Flags representing implementation of JavaScript intrinsics
35    /// made available through the `Javy` global.
36    /// The APIs in this list can be thought of as APIs similar to the ones
37    /// exposed by Node or Deno.
38    ///
39    /// NB: These APIs are meant to be migrated to a runtime-agnostic namespace,
40    /// once efforts like WinterCG can be adopted.
41    ///
42    /// In the near future, Javy will include an extension mechanism, allowing
43    /// users to extend the runtime with non-standard functionality directly
44    /// from the CLI, at this point many, if not most, of these APIs will be
45    /// moved out.
46    #[derive(Debug)]
47    pub(crate) struct JavyIntrinsics: u32 {
48        const STREAM_IO = 1;
49    }
50}
51
52/// A configuration for [`Runtime`](crate::Runtime).
53///
54/// These are the global configuration options to create a [`Runtime`](crate::Runtime),
55/// and customize its behavior.
56pub struct Config {
57    /// JavaScript features.
58    pub(crate) intrinsics: JSIntrinsics,
59    /// Intrinsics exposed through the `Javy` namespace.
60    pub(crate) javy_intrinsics: JavyIntrinsics,
61    /// Whether to override the implementation of JSON.parse and JSON.stringify
62    /// with a Rust implementation that uses a combination for Serde transcoding
63    /// serde_json and simd_json.
64    /// This setting requires the `JSON` intrinsic to be enabled, and the `json`
65    /// crate feature to be enabled as well.
66    pub(crate) simd_json_builtins: bool,
67    /// The threshold to trigger garbage collection. Default is usize::MAX.
68    pub(crate) gc_threshold: usize,
69    /// The limit on the max amount of memory the runtime will use. Default is
70    /// unlimited.
71    pub(crate) memory_limit: usize,
72    /// The limit on the max size of stack the runtime will use. Default is
73    /// 256 * 1024.
74    pub(crate) max_stack_size: usize,
75    /// The stream to use for calls to `console.log`.
76    pub(crate) log_stream: Box<dyn Write>,
77    /// The stream to use for calls to `console.error`.
78    pub(crate) err_stream: Box<dyn Write>,
79}
80
81impl Default for Config {
82    /// Creates a [`Config`] with default values.
83    fn default() -> Self {
84        let mut intrinsics = JSIntrinsics::all();
85        intrinsics.set(JSIntrinsics::TEXT_ENCODING, false);
86        intrinsics.set(JSIntrinsics::WEAK_REF, false);
87        intrinsics.set(JSIntrinsics::PERFORMANCE, false);
88        Self {
89            intrinsics,
90            javy_intrinsics: JavyIntrinsics::empty(),
91            simd_json_builtins: false,
92            gc_threshold: usize::MAX,
93            memory_limit: usize::MAX,
94            max_stack_size: 256 * 1024, // from rquickjs
95            log_stream: Box::new(std::io::stdout()),
96            err_stream: Box::new(std::io::stderr()),
97        }
98    }
99}
100
101impl Config {
102    /// Configures whether the JavaScript `Date` intrinsic will be available.
103    pub fn date(&mut self, enable: bool) -> &mut Self {
104        self.intrinsics.set(JSIntrinsics::DATE, enable);
105        self
106    }
107
108    /// Configures whether the `Eval` intrinsic will be available.
109    pub fn eval(&mut self, enable: bool) -> &mut Self {
110        self.intrinsics.set(JSIntrinsics::EVAL, enable);
111        self
112    }
113
114    /// Configures whether the regular expression compiler will be available.
115    pub fn regexp_compiler(&mut self, enable: bool) -> &mut Self {
116        self.intrinsics.set(JSIntrinsics::REGEXP_COMPILER, enable);
117        self
118    }
119
120    /// Configures whether the `RegExp` intrinsic will be available.
121    pub fn regexp(&mut self, enable: bool) -> &mut Self {
122        self.intrinsics.set(JSIntrinsics::REGEXP, enable);
123        self
124    }
125
126    /// Configures whether the QuickJS native JSON intrinsic will be
127    /// available.
128    pub fn json(&mut self, enable: bool) -> &mut Self {
129        self.intrinsics.set(JSIntrinsics::JSON, enable);
130        self
131    }
132
133    /// Configures whether proxy object creation  will be available.
134    /// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
135    pub fn proxy(&mut self, enable: bool) -> &mut Self {
136        self.intrinsics.set(JSIntrinsics::PROXY, enable);
137        self
138    }
139
140    /// Configures whether the `MapSet` intrinsic will be available.
141    pub fn map_set(&mut self, enable: bool) -> &mut Self {
142        self.intrinsics.set(JSIntrinsics::MAP_SET, enable);
143        self
144    }
145
146    /// Configures whether the `Promise` instrinsic will be available.
147    pub fn promise(&mut self, enable: bool) -> &mut Self {
148        self.intrinsics.set(JSIntrinsics::PROMISE, enable);
149        self
150    }
151
152    /// Configures whether supoort for `BigInt` will be available.
153    pub fn big_int(&mut self, enable: bool) -> &mut Self {
154        self.intrinsics.set(JSIntrinsics::BIG_INT, enable);
155        self
156    }
157
158    /// Configures whether operator overloading wil be supported.
159    pub fn operator_overloading(&mut self, enable: bool) -> &mut Self {
160        self.intrinsics.set(JSIntrinsics::OPERATORS, enable);
161        self
162    }
163
164    /// Configures whether extensions to `BigNum` will be available.
165    pub fn bignum_extension(&mut self, enable: bool) -> &mut Self {
166        self.intrinsics.set(JSIntrinsics::BIGNUM_EXTENSION, enable);
167        self
168    }
169
170    /// Configures whether the `TextEncoding` and `TextDecoding` intrinsics will
171    /// be available. NB: This is partial implementation.
172    pub fn text_encoding(&mut self, enable: bool) -> &mut Self {
173        self.intrinsics.set(JSIntrinsics::TEXT_ENCODING, enable);
174        self
175    }
176
177    /// Whether the `Javy.IO` intrinsic will be available.
178    /// Disabled by default. Note that it is strongly recommended to target
179    /// WASI preview 1 when enabling this configuration. To use this
180    /// configuration with WASI preview 2 or later, you cannot use Javy's
181    /// plugin initialization and it will not be compatible for use with the
182    /// Javy CLI.
183    pub fn javy_stream_io(&mut self, enable: bool) -> &mut Self {
184        self.javy_intrinsics.set(JavyIntrinsics::STREAM_IO, enable);
185        self
186    }
187
188    /// Enables whether the output of console.log will be redirected to
189    /// `stderr`.
190    pub fn redirect_stdout_to_stderr(&mut self, enable: bool) -> &mut Self {
191        self.log_stream = if enable {
192            Box::new(io::stderr())
193        } else {
194            Box::new(io::stdout())
195        };
196        self
197    }
198
199    /// Whether to override the implementation of JSON.parse and JSON.stringify
200    /// with a Rust implementation that uses a combination of Serde transcoding
201    /// serde_json and simd_json for improved performance.
202    /// This setting requires the `JSON` intrinsic to be enabled and the `json`
203    /// crate feature to be enabled as well.
204    /// Disabled by default.
205    #[cfg(feature = "json")]
206    pub fn simd_json_builtins(&mut self, enable: bool) -> &mut Self {
207        self.simd_json_builtins = enable;
208        self
209    }
210
211    /// The number of bytes to use to trigger garbage collection.
212    /// The default is usize::MAX.
213    pub fn gc_threshold(&mut self, bytes: usize) -> &mut Self {
214        self.gc_threshold = bytes;
215        self
216    }
217
218    /// The limit on the max amount of memory the runtime will use. Default is
219    /// unlimited.
220    pub fn memory_limit(&mut self, bytes: usize) -> &mut Self {
221        self.memory_limit = bytes;
222        self
223    }
224
225    /// The limit on the max size of stack the runtime will use. Default is
226    /// 256 * 1024.
227    pub fn max_stack_size(&mut self, bytes: usize) -> &mut Self {
228        self.max_stack_size = bytes;
229        self
230    }
231
232    /// The stream to use for calls to `console.log`.
233    pub fn log_stream(&mut self, stream: Box<dyn Write>) -> &mut Self {
234        self.log_stream = stream;
235        self
236    }
237
238    /// The stream to use for calls to `console.error`.
239    pub fn err_stream(&mut self, stream: Box<dyn Write>) -> &mut Self {
240        self.err_stream = stream;
241        self
242    }
243
244    /// Whether the `WeakRef` instrinsic will be enabled.
245    pub fn weak_ref(&mut self, enable: bool) -> &mut Self {
246        self.intrinsics.set(JSIntrinsics::WEAK_REF, enable);
247        self
248    }
249
250    /// Whether the `Performance` intrinsic will be enabled.
251    pub fn performance(&mut self, enable: bool) -> &mut Self {
252        self.intrinsics.set(JSIntrinsics::PERFORMANCE, enable);
253        self
254    }
255
256    pub(crate) fn validate(self) -> Result<Self> {
257        if self.simd_json_builtins && !self.intrinsics.contains(JSIntrinsics::JSON) {
258            bail!("JSON Intrinsic is required to override JSON.parse and JSON.stringify");
259        }
260
261        Ok(self)
262    }
263}
264
265#[cfg(test)]
266#[cfg(feature = "json")]
267mod tests {
268    use super::Config;
269
270    #[test]
271    fn err_config_validation() {
272        let mut config = Config::default();
273        config.simd_json_builtins(true);
274        config.json(false);
275
276        assert!(config.validate().is_err());
277    }
278
279    #[test]
280    fn ok_config_validation() {
281        let mut config = Config::default();
282        config.simd_json_builtins(true);
283
284        assert!(config.validate().is_ok());
285    }
286}