jsbind/
prelude.rs

1pub use emlite;
2pub use emlite::Console;
3pub use emlite::FromVal;
4
5use alloc::{format, vec};
6
7pub use crate::any::{Any, AnyHandle};
8pub use crate::array::{
9    Array, ArrayBuffer, DataView, Endian, Float32Array, Float64Array, FrozenArray, Int8Array,
10    Int32Array, ObservableArray, TypedArray, Uint8Array, Uint32Array,
11};
12pub use crate::bigint::BigInt;
13pub use crate::date::Date;
14pub use crate::error::*;
15pub use crate::function::{Closure, Function};
16pub use crate::json::JSON;
17pub use crate::map::*;
18pub use crate::math::Math;
19pub use crate::null::Null;
20pub use crate::number::Number;
21pub use crate::object::Object;
22pub use crate::promise::Promise;
23pub use crate::record::Record;
24pub use crate::reflect::Reflect;
25pub use crate::regexp::{RegExp, RegExpFlags};
26pub use crate::response::{fetch, fetch_val};
27pub use crate::set::*;
28pub use crate::string::JsString;
29pub use crate::symbol::Symbol;
30pub use crate::text::{TextDecoder, TextEncoder};
31pub use crate::time::*;
32pub use crate::undefined::Undefined;
33pub use crate::url::URL;
34
35/// Parse `src` with an optional `radix`.  Mirrors `parseInt(str, radix)`.
36///
37/// # Arguments
38/// * `src` - String to parse
39/// * `radix` - Optional radix (2-36)
40///
41/// # Returns
42/// Result containing parsed integer or error
43///
44/// # Examples
45/// ```rust
46/// use jsbind::prelude::*;
47///
48/// let result = parse_int("42", None);
49/// assert!(result.is_ok());
50/// assert_eq!(result.unwrap(), 42);
51///
52/// let error_result = parse_int("not_a_number", None);
53/// assert!(error_result.is_err());
54/// ```
55pub fn parse_int(src: &str, radix: Option<i32>) -> Result<i32, JsError> {
56    let g = emlite::Val::global("parseInt");
57    let result = match radix {
58        Some(r) => {
59            if !(2..=36).contains(&r) {
60                return Err(JsError::new("Radix must be between 2 and 36"));
61            }
62            g.invoke(&[src.into(), r.into()])
63        }
64        None => g.invoke(&[src.into()]),
65    };
66
67    if is_nan(&result) {
68        Err(JsError::new(&format!("Invalid number format: '{}'", src)))
69    } else {
70        Ok(result.as_::<i32>())
71    }
72}
73
74/// Parse a floating-point value – identical to JS `parseFloat(str)`.
75///
76/// # Arguments
77/// * `src` - String to parse
78///
79/// # Returns
80/// Result containing parsed float or error
81///
82/// # Examples
83/// ```rust
84/// use jsbind::prelude::*;
85///
86/// let result = parse_float("3.14");
87/// assert!(result.is_ok());
88/// assert_eq!(result.unwrap(), 3.14);
89///
90/// let error_result = parse_float("not_a_number");
91/// assert!(error_result.is_err());
92/// ```
93pub fn parse_float(src: &str) -> Result<f64, JsError> {
94    let result = emlite::Val::global("parseFloat").invoke(&[src.into()]);
95
96    if is_nan(&result) {
97        Err(JsError::new(&format!("Invalid number format: '{}'", src)))
98    } else {
99        Ok(result.as_::<f64>())
100    }
101}
102
103/// Trait analogous to `wasm-bindgen::JsCast`.
104///
105/// Automatically available on every wrapper that is:
106/// * holds a single `emlite::Val`
107/// * implements `AsRef<Val>` and `Into<Val>`
108pub trait DynCast
109where
110    Self: AsRef<emlite::Val> + Into<emlite::Val> + AsMut<emlite::Val>,
111{
112    fn has_type<T>(&self) -> bool
113    where
114        T: DynCast,
115    {
116        T::is_type_of(self.as_ref())
117    }
118
119    fn dyn_into<T>(self) -> Result<T, Self>
120    where
121        T: DynCast,
122    {
123        if self.has_type::<T>() {
124            Ok(self.unchecked_into())
125        } else {
126            Err(self)
127        }
128    }
129
130    fn dyn_ref<T>(&self) -> Option<&T>
131    where
132        T: DynCast,
133    {
134        if self.has_type::<T>() {
135            Some(self.unchecked_ref())
136        } else {
137            None
138        }
139    }
140
141    fn dyn_mut<T>(&mut self) -> Option<&mut T>
142    where
143        T: DynCast,
144    {
145        if self.has_type::<T>() {
146            Some(self.unchecked_mut())
147        } else {
148            None
149        }
150    }
151
152    fn unchecked_into<T>(self) -> T
153    where
154        T: DynCast,
155    {
156        T::unchecked_from_val(self.into())
157    }
158
159    fn unchecked_ref<T>(&self) -> &T
160    where
161        T: DynCast,
162    {
163        T::unchecked_from_val_ref(self.as_ref())
164    }
165
166    fn unchecked_mut<T>(&mut self) -> &mut T
167    where
168        T: DynCast,
169    {
170        T::unchecked_from_val_mut(self.as_mut())
171    }
172
173    fn is_instance_of<T>(&self) -> bool
174    where
175        T: DynCast,
176    {
177        T::instanceof(self.as_ref())
178    }
179
180    /// Implementation of `val instanceof ThisType`.
181    fn instanceof(val: &emlite::Val) -> bool;
182
183    /// Customisable brand check – defaults to `instanceof`.
184    fn is_type_of(val: &emlite::Val) -> bool {
185        Self::instanceof(val)
186    }
187
188    /// Zero-cost unchecked conversion from `Val` into `Self`.
189    fn unchecked_from_val(v: emlite::Val) -> Self;
190
191    /// Zero-cost unchecked conversion from `&Val` into `&Self`.
192    fn unchecked_from_val_ref(v: &emlite::Val) -> &Self;
193
194    /// Zero-cost unchecked conversion from `&mut Val` into `&mut Self`.
195    fn unchecked_from_val_mut(v: &mut emlite::Val) -> &mut Self;
196}
197
198/// Throws a JS exception.
199#[cold]
200#[inline(never)]
201pub fn throw_str(s: &str) -> ! {
202    throw_val(s.into())
203}
204
205/// Throws a JS exception
206#[cold]
207#[inline(never)]
208pub fn throw_val(s: Any) -> ! {
209    let handle = s.as_handle();
210    core::mem::forget(s);
211    emlite::Val::throw(emlite::Val::take_ownership(handle));
212}
213
214// Implementations copied from wasm-bindgen
215pub trait UnwrapThrowExt<T>: Sized {
216    fn unwrap_throw(self) -> T {
217        let loc = core::panic::Location::caller();
218        let msg = alloc::format!(
219            "called `{}::unwrap_throw()` ({}:{}:{})",
220            core::any::type_name::<Self>(),
221            loc.file(),
222            loc.line(),
223            loc.column()
224        );
225        self.expect_throw(&msg)
226    }
227
228    fn expect_throw(self, message: &str) -> T;
229}
230
231// Implementations copied from wasm-bindgen
232impl<T> UnwrapThrowExt<T> for Option<T> {
233    fn unwrap_throw(self) -> T {
234        const MSG: &str = "called `Option::unwrap_throw()` on a `None` value";
235        if let Some(val) = self {
236            val
237        } else if cfg!(debug_assertions) {
238            let loc = core::panic::Location::caller();
239            let msg = alloc::format!("{} ({}:{}:{})", MSG, loc.file(), loc.line(), loc.column(),);
240
241            throw_str(&msg)
242        } else {
243            throw_str(MSG)
244        }
245    }
246
247    fn expect_throw(self, message: &str) -> T {
248        if let Some(val) = self {
249            val
250        } else if cfg!(debug_assertions) {
251            let loc = core::panic::Location::caller();
252            let msg = alloc::format!(
253                "{} ({}:{}:{})",
254                message,
255                loc.file(),
256                loc.line(),
257                loc.column(),
258            );
259
260            throw_str(&msg)
261        } else {
262            throw_str(message)
263        }
264    }
265}
266
267// Implementations copied from wasm-bindgen
268impl<T, E> UnwrapThrowExt<T> for Result<T, E>
269where
270    E: core::fmt::Debug,
271{
272    fn unwrap_throw(self) -> T {
273        const MSG: &str = "called `Result::unwrap_throw()` on an `Err` value";
274        match self {
275            Ok(val) => val,
276            Err(err) => {
277                if cfg!(debug_assertions) {
278                    let loc = core::panic::Location::caller();
279                    let msg = alloc::format!(
280                        "{} ({}:{}:{}): {:?}",
281                        MSG,
282                        loc.file(),
283                        loc.line(),
284                        loc.column(),
285                        err
286                    );
287
288                    throw_str(&msg)
289                } else {
290                    throw_str(MSG)
291                }
292            }
293        }
294    }
295
296    fn expect_throw(self, message: &str) -> T {
297        match self {
298            Ok(val) => val,
299            Err(err) => {
300                if cfg!(debug_assertions) {
301                    let loc = core::panic::Location::caller();
302                    let msg = alloc::format!(
303                        "{} ({}:{}:{}): {:?}",
304                        message,
305                        loc.file(),
306                        loc.line(),
307                        loc.column(),
308                        err
309                    );
310
311                    throw_str(&msg)
312                } else {
313                    throw_str(message)
314                }
315            }
316        }
317    }
318}
319
320/// Encode string to base64.
321///
322/// # Arguments  
323/// * `data` - String to encode
324///
325/// # Returns
326/// Result containing base64 encoded string or error
327///
328/// # Examples
329/// ```rust
330/// use jsbind::prelude::*;
331///
332/// let input = JsString::from("Hello World");
333/// let result = btoa(&input);
334/// assert!(result.is_ok());
335/// ```
336pub fn btoa(data: &JsString) -> Result<JsString, JsError> {
337    let result = emlite::Val::global("btoa").invoke(&[data.into()]);
338    result.as_::<Result<JsString, JsError>>()
339}
340
341/// Decode base64 string.
342///
343/// # Arguments
344/// * `encoded` - Base64 encoded string  
345///
346/// # Returns
347/// Result containing decoded string or error
348///
349/// # Examples
350/// ```rust
351/// use jsbind::prelude::*;
352///
353/// let encoded = JsString::from("SGVsbG8gV29ybGQ=");
354/// let result = atob(&encoded);
355/// assert!(result.is_ok());
356/// ```
357pub fn atob(encoded: &JsString) -> Result<JsString, JsError> {
358    let result = emlite::Val::global("atob").invoke(&[encoded.into()]);
359    result.as_::<Result<JsString, JsError>>()
360}
361
362/// Checks if a value is NaN.
363///
364/// # Arguments
365/// * `value` - Value to check
366///
367/// # Returns
368/// `true` if the value is NaN, `false` otherwise
369///
370/// # Examples
371/// ```rust
372/// use jsbind::prelude::*;
373///
374/// assert!(is_nan(&(0.0/0.0).into()));
375/// assert!(!is_nan(&42.into()));
376/// ```
377pub fn is_nan<V: Into<emlite::Val>>(value: V) -> bool {
378    emlite::Val::global("isNaN")
379        .invoke(&[value.into()])
380        .as_::<bool>()
381}
382
383/// Queues a microtask to be executed.
384///
385/// # Arguments
386/// * `callback` - Function to execute as microtask
387///
388/// # Examples
389/// ```rust
390/// use jsbind::prelude::*;
391///
392/// let callback = Function::from_closure(|| {
393///     Console::get().log(&["Microtask executed!".into()]);
394/// });
395/// queue_microtask(&callback);
396/// ```
397pub fn queue_microtask<C: Into<emlite::Val>>(callback: C) {
398    emlite::Val::global("queueMicrotask").invoke(&[callback.into()]);
399}
400
401/// Dynamically imports a module.
402///
403/// # Arguments
404/// * `specifier` - Module specifier to import
405///
406/// # Returns
407/// Promise that resolves to Result containing the module namespace object
408///
409/// # Examples
410/// ```rust
411/// use jsbind::prelude::*;
412///
413/// let import_promise = import_module("./my-module.js");
414/// // import_promise resolves to the module's exports
415/// ```
416pub fn import_module(specifier: &str) -> Promise<Result<Object, JsError>> {
417    let import_promise = emlite::Val::global("import").invoke(&[specifier.into()]);
418    Promise::take_ownership(import_promise.as_handle())
419}
420
421/// Requires a CommonJS module.
422///
423/// # Arguments
424/// * `specifier` - Module specifier to require
425///
426/// # Returns
427/// Result containing the module exports
428///
429/// # Examples
430/// ```rust
431/// use jsbind::prelude::*;
432///
433/// let exports = require_module("fs")?;
434/// // exports contains the CommonJS module exports
435/// ```
436pub fn require_module(specifier: &str) -> Result<Object, JsError> {
437    let require_fn = emlite::Val::global("require");
438    if require_fn.is_undefined() {
439        return Err(JsError::new("require is not available in this environment"));
440    }
441
442    let module_exports = require_fn.invoke(&[specifier.into()]);
443    Ok(Object::from_val(&module_exports.as_::<Any>()))
444}
445
446/// Creates a require function using import.meta.url.
447///
448/// # Arguments
449/// * `import_meta_url` - The import.meta.url value
450///
451/// # Returns
452/// Result containing require function for CommonJS module loading
453///
454/// # Examples
455/// ```rust
456/// use jsbind::prelude::*;
457///
458/// let import_meta = Any::global("import").get("meta");
459/// let import_meta_url = import_meta.get("url");
460/// let require_fn = create_require(&import_meta_url)?;
461/// // require_fn can now be used to load CommonJS modules
462/// ```
463pub fn create_require<V: Into<emlite::Val>>(import_meta_url: V) -> Result<Function, JsError> {
464    let module_obj = emlite::Val::global("module");
465    if module_obj.is_undefined() {
466        return Err(JsError::new(
467            "module.createRequire not supported in this environment",
468        ));
469    }
470
471    let create_require_fn = module_obj.get("createRequire");
472    if create_require_fn.is_undefined() {
473        return Err(JsError::new("module.createRequire not available"));
474    }
475
476    let require_fn = create_require_fn.invoke(&[import_meta_url.into()]);
477    Ok(Function::from_val(&require_fn.as_::<Any>()))
478}
479
480/// Options for structured cloning operations.
481#[derive(Clone, Debug)]
482pub struct JsStructuredSerializeOptions {
483    inner: emlite::Val,
484}
485
486impl JsStructuredSerializeOptions {
487    /// Creates a new JsStructuredSerializeOptions object.
488    pub fn new() -> Self {
489        Self {
490            inner: emlite::Val::object(),
491        }
492    }
493
494    /// Gets the transfer list for transferable objects.
495    pub fn transfer(&self) -> Option<TypedArray<Object>> {
496        let val = self.inner.get("transfer");
497        if val.is_undefined() {
498            None
499        } else {
500            Some(val.as_::<TypedArray<Object>>())
501        }
502    }
503
504    /// Sets the transfer list for transferable objects.
505    pub fn set_transfer(&self, transfer: &TypedArray<Object>) {
506        self.inner.set("transfer", transfer);
507    }
508}
509
510impl Default for JsStructuredSerializeOptions {
511    fn default() -> Self {
512        Self::new()
513    }
514}
515
516impl AsRef<emlite::Val> for JsStructuredSerializeOptions {
517    fn as_ref(&self) -> &emlite::Val {
518        &self.inner
519    }
520}
521
522impl From<JsStructuredSerializeOptions> for emlite::Val {
523    fn from(options: JsStructuredSerializeOptions) -> Self {
524        options.inner
525    }
526}
527
528impl From<&JsStructuredSerializeOptions> for emlite::Val {
529    fn from(options: &JsStructuredSerializeOptions) -> Self {
530        options.inner.clone()
531    }
532}
533
534/// Performs a structured clone of a value.
535///
536/// # Arguments
537/// * `value` - The value to clone
538/// * `options` - Optional structured clone options
539///
540/// # Returns
541/// Deep clone of the input value
542///
543/// # Examples
544/// ```rust
545/// use jsbind::prelude::*;
546///
547/// let obj = Object::new();
548/// obj.set("key", "value");
549///
550/// let cloned = structured_clone(&obj, None);
551/// // cloned is a deep copy of obj
552/// ```
553pub fn structured_clone<T>(value: &T, options: Option<&JsStructuredSerializeOptions>) -> T
554where
555    T: emlite::FromVal + AsRef<emlite::Val>,
556{
557    let args = match options {
558        Some(opts) => vec![value.as_ref().clone(), opts.into()],
559        None => vec![value.as_ref().clone()],
560    };
561
562    emlite::Val::global("structuredClone")
563        .invoke(&args)
564        .as_::<T>()
565}