nanom/
lib.rs

1use std::{
2    any::{Any, TypeId},
3    convert::Infallible,
4    error, fmt, hint,
5    mem::{self, MaybeUninit},
6    num::TryFromIntError,
7    panic::{self, AssertUnwindSafe},
8};
9
10pub mod napi;
11pub mod typing;
12
13pub use nanom_derive::JsObject;
14use typing::{Type, TypeRef, Undefined};
15
16#[macro_export]
17macro_rules! register_module {
18    ($module:expr) => {
19        #[no_mangle]
20        unsafe extern "C" fn napi_register_module_v1(
21            env: $crate::napi::Env,
22            exports: $crate::napi::Value,
23        ) -> $crate::napi::Value {
24            $crate::register_object_as_module(env, exports, $module)
25        }
26    };
27}
28
29pub unsafe fn register_object_as_module(
30    env: napi::Env,
31    exports: napi::Value,
32    module: impl IntoJs,
33) -> napi::Value {
34    napi_sys::setup();
35
36    let register = || -> Result<(), ConversionError> {
37        let module = module.into_js(env)?;
38
39        let names = env.get_property_names(module)?;
40        for name in env.get_array_iter(names)? {
41            let name = name?;
42            let value = env.get_property(module, name)?;
43            env.set_property(exports, name, value)?;
44        }
45
46        Ok(())
47    };
48
49    if let Err(err) = register() {
50        return env.must_throw(&format!("module registration failed: {err}"));
51    }
52
53    exports
54}
55
56pub trait TsType: 'static {
57    fn ts_type() -> Type;
58
59    fn ts_type_ref() -> TypeRef {
60        TypeRef {
61            id: TypeId::of::<Self>(),
62            get_type: || Self::ts_type(),
63        }
64    }
65}
66
67pub trait FromJs: TsType + Sized {
68    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError>;
69}
70
71pub trait IntoJs: TsType {
72    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError>;
73}
74
75pub trait JsObject: Sized + 'static {
76    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError>;
77    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError>;
78    fn ts_type() -> Type;
79}
80
81impl<T: JsObject> TsType for T {
82    fn ts_type() -> Type {
83        <T as JsObject>::ts_type()
84    }
85}
86
87impl<T: JsObject> IntoJs for T {
88    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
89        JsObject::into_js(self, env)
90    }
91}
92
93impl<T: JsObject> FromJs for T {
94    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
95        JsObject::from_js(env, value)
96    }
97}
98
99impl TsType for () {
100    fn ts_type() -> Type {
101        Type::Undefined
102    }
103}
104
105impl FromJs for () {
106    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
107        unsafe {
108            if env.type_of(value)? != napi::Type::Undefined {
109                return Err(ConversionError::ExpectedUndefined);
110            }
111        }
112
113        Ok(())
114    }
115}
116
117impl IntoJs for () {
118    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
119        unsafe { env.get_undefined() }.map_err(ConversionError::Napi)
120    }
121}
122
123pub struct Null;
124
125impl TsType for Null {
126    fn ts_type() -> Type {
127        Type::Null
128    }
129}
130
131impl FromJs for Null {
132    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
133        unsafe {
134            if env.type_of(value)? != napi::Type::Null {
135                return Err(ConversionError::ExpectedNull);
136            }
137        }
138
139        Ok(Null)
140    }
141}
142
143impl IntoJs for Null {
144    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
145        unsafe { env.get_null() }.map_err(ConversionError::Napi)
146    }
147}
148
149impl FromJs for bool {
150    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
151        unsafe { env.get_boolean_value(value) }.map_err(ConversionError::Napi)
152    }
153}
154
155impl IntoJs for bool {
156    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
157        unsafe { env.get_boolean(self) }.map_err(ConversionError::Napi)
158    }
159}
160
161impl TsType for bool {
162    fn ts_type() -> Type {
163        Type::Boolean
164    }
165}
166
167impl TsType for String {
168    fn ts_type() -> Type {
169        Type::String
170    }
171}
172
173impl FromJs for String {
174    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
175        unsafe { env.get_value_string(value) }.map_err(ConversionError::Napi)
176    }
177}
178
179impl IntoJs for String {
180    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
181        unsafe { env.create_string(&self) }.map_err(ConversionError::Napi)
182    }
183}
184
185impl TsType for f64 {
186    fn ts_type() -> Type {
187        Type::Number
188    }
189}
190
191impl FromJs for f64 {
192    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
193        unsafe { env.get_value_double(value) }.map_err(ConversionError::Napi)
194    }
195}
196
197impl IntoJs for f64 {
198    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
199        unsafe { env.create_double(self) }.map_err(ConversionError::Napi)
200    }
201}
202
203impl TsType for f32 {
204    fn ts_type() -> Type {
205        Type::Number
206    }
207}
208
209impl FromJs for f32 {
210    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
211        Ok(<f64 as FromJs>::from_js(env, value)? as f32)
212    }
213}
214
215impl IntoJs for f32 {
216    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
217        IntoJs::into_js(self as f64, env)
218    }
219}
220
221impl TsType for i64 {
222    fn ts_type() -> Type {
223        Type::Number
224    }
225}
226
227impl FromJs for i64 {
228    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
229        let float: f64 = FromJs::from_js(env, value)?;
230        if float as i64 as f64 != float {
231            return Err(ConversionError::IntLoss);
232        }
233
234        Ok(float as i64)
235    }
236}
237
238impl IntoJs for i64 {
239    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
240        if self as f64 as i64 != self {
241            return Err(ConversionError::JsNumberLoss);
242        }
243
244        IntoJs::into_js(self as f64, env)
245    }
246}
247
248impl TsType for i32 {
249    fn ts_type() -> Type {
250        Type::Number
251    }
252}
253
254impl FromJs for i32 {
255    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
256        Ok(<i64 as FromJs>::from_js(env, value)?.try_into()?)
257    }
258}
259
260impl IntoJs for i32 {
261    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
262        IntoJs::into_js(self as i64, env)
263    }
264}
265
266impl TsType for i16 {
267    fn ts_type() -> Type {
268        Type::Number
269    }
270}
271
272impl FromJs for i16 {
273    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
274        Ok(<i64 as FromJs>::from_js(env, value)?.try_into()?)
275    }
276}
277
278impl IntoJs for i16 {
279    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
280        IntoJs::into_js(self as i64, env)
281    }
282}
283
284impl TsType for i8 {
285    fn ts_type() -> Type {
286        Type::Number
287    }
288}
289
290impl FromJs for i8 {
291    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
292        Ok(<i64 as FromJs>::from_js(env, value)?.try_into()?)
293    }
294}
295
296impl IntoJs for i8 {
297    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
298        IntoJs::into_js(self as i64, env)
299    }
300}
301
302impl TsType for isize {
303    fn ts_type() -> Type {
304        Type::Number
305    }
306}
307
308impl FromJs for isize {
309    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
310        Ok(<i64 as FromJs>::from_js(env, value)?.try_into()?)
311    }
312}
313
314impl IntoJs for isize {
315    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
316        IntoJs::into_js(self as i64, env)
317    }
318}
319
320impl TsType for u64 {
321    fn ts_type() -> Type {
322        Type::Number
323    }
324}
325
326impl FromJs for u64 {
327    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
328        Ok(<i64 as FromJs>::from_js(env, value)?.try_into()?)
329    }
330}
331
332impl IntoJs for u64 {
333    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
334        if self as f64 as u64 != self {
335            return Err(ConversionError::JsNumberLoss);
336        }
337
338        IntoJs::into_js(self as f64, env)
339    }
340}
341
342impl TsType for u32 {
343    fn ts_type() -> Type {
344        Type::Number
345    }
346}
347
348impl FromJs for u32 {
349    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
350        Ok(<u64 as FromJs>::from_js(env, value)?.try_into()?)
351    }
352}
353
354impl IntoJs for u32 {
355    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
356        IntoJs::into_js(self as u64, env)
357    }
358}
359
360impl TsType for u16 {
361    fn ts_type() -> Type {
362        Type::Number
363    }
364}
365
366impl FromJs for u16 {
367    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
368        Ok(<u64 as FromJs>::from_js(env, value)?.try_into()?)
369    }
370}
371
372impl IntoJs for u16 {
373    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
374        IntoJs::into_js(self as u64, env)
375    }
376}
377
378impl TsType for u8 {
379    fn ts_type() -> Type {
380        Type::Number
381    }
382}
383
384impl FromJs for u8 {
385    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
386        Ok(<u64 as FromJs>::from_js(env, value)?.try_into()?)
387    }
388}
389
390impl IntoJs for u8 {
391    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
392        IntoJs::into_js(self as u64, env)
393    }
394}
395
396impl TsType for usize {
397    fn ts_type() -> Type {
398        Type::Number
399    }
400}
401
402impl FromJs for usize {
403    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
404        Ok(<u64 as FromJs>::from_js(env, value)?.try_into()?)
405    }
406}
407
408impl IntoJs for usize {
409    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
410        IntoJs::into_js(self as u64, env)
411    }
412}
413
414impl<T: TsType> TsType for Vec<T> {
415    fn ts_type() -> Type {
416        Type::Array(Box::new(T::ts_type_ref()))
417    }
418}
419
420impl<T: IntoJs> IntoJs for Vec<T> {
421    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
422        let array = unsafe { env.create_array_with_length(self.len()) }?;
423
424        for (i, item) in self.into_iter().enumerate() {
425            unsafe {
426                env.set_element(array, i as u32, item.into_js(env)?)?;
427            }
428        }
429
430        Ok(array)
431    }
432}
433
434impl<T: FromJs> FromJs for Vec<T> {
435    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
436        let len = unsafe { env.get_array_len(value)? };
437
438        let mut result = Vec::with_capacity(len as usize);
439
440        for i in 0..len {
441            result.push(T::from_js(env, unsafe { env.get_element(value, i)? })?);
442        }
443
444        Ok(result)
445    }
446}
447
448impl<T: TsType> TsType for Option<T> {
449    fn ts_type() -> Type {
450        Type::Optional(Box::new(T::ts_type_ref()))
451    }
452}
453
454impl<T: FromJs> FromJs for Option<T> {
455    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
456        unsafe {
457            if env.type_of(value)? == napi::Type::Null {
458                return Ok(None);
459            }
460        }
461
462        Ok(Some(T::from_js(env, value)?))
463    }
464}
465
466impl<T: IntoJs> IntoJs for Option<T> {
467    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
468        match self {
469            Some(value) => value.into_js(env),
470            None => Null.into_js(env),
471        }
472    }
473}
474
475impl<T: TsType, const N: usize> TsType for [T; N] {
476    fn ts_type() -> Type {
477        Type::Tuple(vec![T::ts_type_ref(); N])
478    }
479}
480
481impl<T: FromJs, const N: usize> FromJs for [T; N] {
482    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
483        let len = unsafe { env.get_array_len(value)? };
484
485        if len != N as u32 {
486            return Err(ConversionError::InvalidTupleLength {
487                expected: N,
488                actual: len as usize,
489            });
490        }
491
492        let mut this = [const { MaybeUninit::uninit() }; N];
493
494        let get_element = |i: u32| -> Result<_, ConversionError> {
495            Ok(T::from_js(env, unsafe { env.get_element(value, i)? })?)
496        };
497
498        for i in 0..len {
499            match get_element(i) {
500                Ok(value) => {
501                    this[i as usize].write(value);
502                }
503                Err(err) => {
504                    for i in 0..i {
505                        unsafe {
506                            this[i as usize].assume_init_drop();
507                        }
508                    }
509                    return Err(err);
510                }
511            }
512        }
513
514        let this: [T; N] = unsafe { mem::transmute_copy(&this) };
515
516        Ok(this)
517    }
518}
519
520impl<T: IntoJs, const N: usize> IntoJs for [T; N] {
521    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
522        let array = unsafe { env.create_array_with_length(N) }?;
523
524        for (i, item) in self.into_iter().enumerate() {
525            unsafe {
526                env.set_element(array, i as u32, item.into_js(env)?)?;
527            }
528        }
529
530        Ok(array)
531    }
532}
533
534impl TsType for *mut [u8] {
535    fn ts_type() -> Type {
536        Type::DataView
537    }
538}
539
540impl FromJs for *mut [u8] {
541    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
542        unsafe { env.get_data_view(value) }.map_err(ConversionError::Napi)
543    }
544}
545
546pub struct Function<F, A> {
547    function: F,
548    _args: std::marker::PhantomData<fn(A)>,
549}
550
551pub fn throwing_function<A, R, F: Fn(A) -> R>(function: F) -> Function<F, A> {
552    Function {
553        function,
554        _args: std::marker::PhantomData,
555    }
556}
557
558pub fn function<A, R, F: Fn(A) -> R + 'static>(
559    function: F,
560) -> Function<impl Fn(A) -> Result<R, Infallible> + 'static, A> {
561    Function {
562        function: move |arg| Ok(function(arg)),
563        _args: std::marker::PhantomData,
564    }
565}
566
567#[derive(Debug)]
568pub enum ConversionError {
569    Napi(napi::Status),
570    InObjectField {
571        field_name: &'static str,
572        error: Box<ConversionError>,
573    },
574    InArrayElement {
575        index: usize,
576        error: Box<ConversionError>,
577    },
578    InKind(napi::Status),
579    InEnumValue(Box<ConversionError>),
580    InvalidTupleLength {
581        expected: usize,
582        actual: usize,
583    },
584    ExpectedNull,
585    ExpectedUndefined,
586    InvalidKind(String),
587    IntLoss,
588    JsNumberLoss,
589    FloatIsNegative,
590    Int(TryFromIntError),
591}
592
593impl From<napi::Status> for ConversionError {
594    fn from(status: napi::Status) -> Self {
595        ConversionError::Napi(status)
596    }
597}
598
599impl From<TryFromIntError> for ConversionError {
600    fn from(error: TryFromIntError) -> Self {
601        ConversionError::Int(error)
602    }
603}
604
605impl error::Error for ConversionError {}
606impl fmt::Display for ConversionError {
607    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608        match self {
609            ConversionError::Napi(status) => write!(f, "napi error: {status:?}"),
610            ConversionError::InObjectField { field_name, error } => {
611                write!(f, "in field \"{field_name}\": {error}")
612            }
613            ConversionError::InArrayElement { index, error } => {
614                write!(f, "at array element {index}: {error}")
615            }
616            ConversionError::ExpectedNull => write!(f, "expected null"),
617            ConversionError::ExpectedUndefined => write!(f, "expected undefined"),
618            ConversionError::InvalidKind(kind) => write!(f, "invalid kind \"{kind}\""),
619            ConversionError::InEnumValue(error) => {
620                write!(f, "in enum value: {error}")
621            }
622            ConversionError::InKind(error) => write!(f, "in enum kind: {error:?}"),
623            ConversionError::InvalidTupleLength { expected, actual } => {
624                write!(
625                    f,
626                    "expected tuple of length {expected}, but length was {actual}"
627                )
628            }
629            ConversionError::IntLoss => write!(f, "conversion to integer lost precision"),
630            ConversionError::JsNumberLoss => write!(f, "conversion to js number lost precision"),
631            ConversionError::FloatIsNegative => write!(f, "expected non-negative js number"),
632            ConversionError::Int(error) => write!(f, "integer conversion failed: {error}"),
633        }
634    }
635}
636
637#[derive(Debug)]
638enum Error {
639    Rust(String),
640    InFunctionArgument {
641        index: usize,
642        error: ConversionError,
643    },
644    InFunctionReturnValue(ConversionError),
645}
646
647impl error::Error for Error {}
648impl fmt::Display for Error {
649    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650        match self {
651            Error::Rust(error) => write!(f, "rust error: {error}"),
652            Error::InFunctionArgument { index, error } => {
653                write!(f, "argument {index} conversion failed: {error}")
654            }
655            Error::InFunctionReturnValue(error) => {
656                write!(f, "return value conversion failed: {error}")
657            }
658        }
659    }
660}
661
662fn wrap_function<
663    const N: usize,
664    R: IntoJs,
665    F: Fn(napi::Env, [napi::Value; N]) -> Result<R, Error> + 'static,
666>(
667    env: napi::Env,
668    function: F,
669) -> Result<napi::Value, ConversionError> {
670    unsafe {
671        env.create_function(move |env, args| {
672            function(env, args)?
673                .into_js(env)
674                .map_err(Error::InFunctionReturnValue)
675        })
676        .map_err(ConversionError::Napi)
677    }
678}
679
680macro_rules! impl_into_js_for_function {
681    ($($args:ident $idx:literal),*) => {
682        impl<$($args: TsType,)* R: TsType, E, F: Fn(($($args,)*)) -> Result<R, E> + 'static> TsType
683            for Function<F, ($($args,)*)>
684        {
685            fn ts_type() -> Type {
686                Type::Function {
687                    args: vec![$($args::ts_type_ref()),*],
688                    return_type: Box::new(R::ts_type_ref()),
689                }
690            }
691        }
692
693        impl<$($args: FromJs,)* R: IntoJs, E: fmt::Display + 'static, F: Fn(($($args,)*)) -> Result<R, E> + 'static> IntoJs
694            for Function<F, ($($args,)*)>
695        {
696            fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
697                #[allow(non_snake_case, unused_variables)]
698                wrap_function(env, move |env, [$($args),*]| {
699                    (self.function)(($(FromJs::from_js(env, $args).map_err(|error| Error::InFunctionArgument { error, index: $idx })?,)*),).map_err(|err| Error::Rust(format!("{err}")))
700                })
701            }
702        }
703    };
704}
705
706impl_into_js_for_function!();
707impl_into_js_for_function!(A1 1);
708impl_into_js_for_function!(A1 1, A2 2);
709impl_into_js_for_function!(A1 1, A2 2, A3 3);
710impl_into_js_for_function!(A1 1, A2 2, A3 3, A4 4);
711impl_into_js_for_function!(A1 1, A2 2, A3 3, A4 4, A5 5);
712impl_into_js_for_function!(A1 1, A2 2, A3 3, A4 4, A5 5, A6 6);
713impl_into_js_for_function!(A1 1, A2 2, A3 3, A4 4, A5 5, A6 6, A7 7);
714impl_into_js_for_function!(A1 1, A2 2, A3 3, A4 4, A5 5, A6 6, A7 7, A8 8);
715impl_into_js_for_function!(A1 1, A2 2, A3 3, A4 4, A5 5, A6 6, A7 7, A8 8, A9 9);
716impl_into_js_for_function!(A1 1, A2 2, A3 3, A4 4, A5 5, A6 6, A7 7, A8 8, A9 9, A10 10);
717
718#[macro_export]
719macro_rules! object {
720    ($($name:ident: $value:expr),*$(,)?) => {
721        {
722            #![allow(non_camel_case_types)]
723            #![allow(non_snake_case)]
724
725            struct Object<$($name),*> {
726                $($name: $name,)*
727            }
728
729            impl <$($name: ::nanom::IntoJs),*> ::nanom::IntoJs for Object<$($name),*> {
730                fn into_js(self, env: ::nanom::napi::Env) -> ::std::result::Result<::nanom::napi::Value, ::nanom::ConversionError> {
731                    unsafe {
732                        let mut object = env.create_object()?;
733
734                        $(
735                            (|| -> ::std::result::Result<(), ::nanom::ConversionError> {
736                                env.set_property(object, env.create_string(stringify!($name))?, self.$name.into_js(env)?)?;
737                                ::std::result::Result::Ok(())
738                            })().map_err(|err| ::nanom::ConversionError::InObjectField { error: Box::new(err), field_name: stringify!($name) })?;
739                        )*
740
741                        Ok(object)
742                    }
743                }
744            }
745
746            impl <$($name: ::nanom::TsType),*> ::nanom::TsType for Object<$($name),*> {
747                fn ts_type() -> ::nanom::typing::Type {
748                    let mut fields = ::std::collections::HashMap::new();
749
750                    $(
751                        fields.insert(stringify!($name).to_string(), <$name as ::nanom::TsType>::ts_type_ref());
752                    )*
753
754                    ::nanom::typing::Type::Object(fields)
755                }
756            }
757
758            Object {
759                $($name: $value,)*
760            }
761        }
762    };
763}
764
765pub struct AsyncWork<F> {
766    fun: F,
767}
768
769impl<F> AsyncWork<F> {
770    pub fn new(fun: F) -> Self {
771        Self { fun }
772    }
773}
774
775impl<F, R, E> TsType for AsyncWork<F>
776where
777    F: FnOnce() -> Result<R, E> + Send + 'static,
778    R: TsType + 'static,
779    E: fmt::Display + 'static,
780{
781    fn ts_type() -> Type {
782        Type::Promise(Box::new(R::ts_type_ref()))
783    }
784}
785
786impl<F, R, E> IntoJs for AsyncWork<F>
787where
788    F: FnOnce() -> Result<R, E> + Send + 'static,
789    R: IntoJs + 'static,
790    E: fmt::Display + 'static,
791{
792    fn into_js(self, env: napi::Env) -> Result<napi::Value, ConversionError> {
793        unsafe {
794            let (promise, deferred) = env.create_promise()?;
795            let work: AsyncWorkDeferred<F, R, E> = AsyncWorkDeferred {
796                deferred,
797                state: AsyncWorkState::Pending(self.fun),
798            };
799
800            env.create_and_queue_async_work(work)?;
801
802            Ok(promise)
803        }
804    }
805}
806
807enum AsyncWorkState<F, R, E> {
808    Pending(F),
809    Completed(Result<Result<R, E>, Box<dyn Any + Send>>),
810    None,
811}
812
813struct AsyncWorkDeferred<F, R, E> {
814    state: AsyncWorkState<F, R, E>,
815    deferred: napi::Deferred,
816}
817
818unsafe impl<F, R, E> Send for AsyncWorkDeferred<F, R, E> {}
819
820impl<F, R, E> napi::AsyncWork for AsyncWorkDeferred<F, R, E>
821where
822    F: FnOnce() -> Result<R, E> + Send + 'static,
823    R: IntoJs + 'static,
824    E: fmt::Display + 'static,
825{
826    fn exec(&mut self) {
827        let AsyncWorkState::Pending(fun) = mem::replace(&mut self.state, AsyncWorkState::None)
828        else {
829            unsafe { hint::unreachable_unchecked() };
830        };
831        let result = panic::catch_unwind(AssertUnwindSafe(fun));
832        self.state = AsyncWorkState::Completed(result);
833    }
834
835    fn complete(self, env: napi::Env) {
836        unsafe {
837            let AsyncWorkState::Completed(result) = self.state else {
838                hint::unreachable_unchecked();
839            };
840
841            let result = match result {
842                Ok(result) => result,
843                Err(info) => {
844                    let message = if let Some(string) = info.downcast_ref::<String>() {
845                        &string
846                    } else if let Some(string) = info.downcast_ref::<&str>() {
847                        string
848                    } else {
849                        "unknown panic"
850                    };
851
852                    let Ok(error) = env.create_string(message) else {
853                        napi::fatal_error(
854                            "",
855                            &format!(
856                                "failed to create error string when rejecting promise: {message}"
857                            ),
858                        );
859                    };
860                    let reject = env.promise_reject(self.deferred, error);
861                    if let Err(err) = reject {
862                        napi::fatal_error("", &format!("failed to reject promise: {err:?}"));
863                    }
864                    return;
865                }
866            };
867
868            let result = match result {
869                Ok(result) => result,
870                Err(err) => {
871                    let error_str = format!("{err}");
872                    let Ok(error) = env.create_string(&error_str) else {
873                        napi::fatal_error(
874                            "",
875                            &format!("failed to create error string when rejecting promise: {err}"),
876                        );
877                    };
878                    let reject = env.promise_reject(self.deferred, error);
879                    if let Err(err) = reject {
880                        napi::fatal_error("", &format!("failed to reject promise: {err:?}"));
881                    }
882                    return;
883                }
884            };
885
886            let result = match result.into_js(env) {
887                Ok(result) => env.promise_resolve(self.deferred, result),
888                Err(err) => {
889                    let error_str = format!("{err}");
890                    let Ok(error) = env.create_string(&error_str) else {
891                        napi::fatal_error(
892                            "",
893                            &format!("failed to create error string when rejecting promise: {err}"),
894                        );
895                    };
896
897                    env.promise_reject(self.deferred, error)
898                }
899            };
900
901            if let Err(err) = result {
902                napi::fatal_error("", &format!("failed to resolve promise: {err:?}"));
903            }
904        };
905    }
906
907    fn failed(self, env: napi::Env, status: napi::Status) {
908        unsafe {
909            let error_str = format!("{status:?}");
910            let Ok(error) = env.create_string(&error_str) else {
911                napi::fatal_error(
912                    "",
913                    &format!("failed to create error string when rejecting promise: {status:?}"),
914                );
915            };
916
917            let result = env.promise_reject(self.deferred, error);
918            if let Err(err) = result {
919                napi::fatal_error("", &format!("failed to resolve promise: {err:?}"));
920            }
921        }
922    }
923}
924
925pub struct ThreadSafeFunction<A> {
926    fun: napi::ThreadSafeFunction<A>,
927}
928
929unsafe impl<A: Send> Send for ThreadSafeFunction<A> {}
930unsafe impl<A: Send> Sync for ThreadSafeFunction<A> {}
931
932impl<A> Clone for ThreadSafeFunction<A> {
933    fn clone(&self) -> Self {
934        Self {
935            fun: self.fun.clone(),
936        }
937    }
938}
939
940pub trait Args: 'static {
941    fn create_args(self, env: napi::Env) -> Result<Vec<napi::Value>, ConversionError>;
942    fn args_type() -> Vec<TypeRef>;
943}
944
945impl<A: Args> napi::Args for A {
946    type Error = ConversionError;
947
948    fn create_args(self, env: napi::Env) -> std::result::Result<Vec<napi::Value>, Self::Error> {
949        Args::create_args(self, env)
950    }
951}
952
953macro_rules! impl_args {
954    ($($args:ident $idx:tt),*) => {
955        impl<$($args: IntoJs),*> Args for ($($args,)*) {
956            fn create_args(self, #[allow(unused_variables)] env: napi::Env) -> Result<Vec<napi::Value>, ConversionError> {
957                Ok(vec![$($args::into_js(self.$idx, env)?),*])
958            }
959
960            fn args_type() -> Vec<TypeRef> {
961                vec![$($args::ts_type_ref()),*]
962            }
963        }
964    };
965}
966
967impl_args!();
968impl_args!(A1 0);
969impl_args!(A1 0, A2 1);
970impl_args!(A1 0, A2 1, A3 2);
971impl_args!(A1 0, A2 1, A3 2, A4 3);
972impl_args!(A1 0, A2 1, A3 2, A4 3, A5 4);
973impl_args!(A1 0, A2 1, A3 2, A4 3, A5 4, A6 5);
974impl_args!(A1 0, A2 1, A3 2, A4 3, A5 4, A6 5, A7 6);
975impl_args!(A1 0, A2 1, A3 2, A4 3, A5 4, A6 5, A7 6, A8 7);
976impl_args!(A1 0, A2 1, A3 2, A4 3, A5 4, A6 5, A7 6, A8 7, A9 8);
977impl_args!(A1 0, A2 1, A3 2, A4 3, A5 4, A6 5, A7 6, A8 7, A9 8, A10 9);
978
979impl<A: Args> TsType for ThreadSafeFunction<A> {
980    fn ts_type() -> Type {
981        Type::Function {
982            args: A::args_type(),
983            return_type: Box::new(Undefined::ts_type_ref()),
984        }
985    }
986}
987
988impl<A: Args> FromJs for ThreadSafeFunction<A> {
989    fn from_js(env: napi::Env, value: napi::Value) -> Result<Self, ConversionError> {
990        let fun = unsafe { env.create_thread_safe_function(value)? };
991        Ok(ThreadSafeFunction { fun })
992    }
993}
994
995impl<A: Args> ThreadSafeFunction<A> {
996    pub fn call(&self, args: A) -> Result<(), napi::Status> {
997        unsafe { self.fun.call(args) }
998    }
999}