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}