feat(napi): support Return generic of ThreadsafeFunction (#1997)

* feat(napi): support to use tuple with either (#1993)

`Either` uses `ValidateNapiValue` + `TypeName` to validate and report error on value not being matched. So there's no way to remove these super traits from it. So I implemented these types to `Tuple` types.

* feat(napi): support `Return` generic of ThreadsafeFunction

* depracate JsFunction

* CalleeHandled tsfn should handle Result in callback

* Pass env to call_with_return_value callback

* Fix compile

* clippy fix

* Fix electron test

* Function args

---------

Co-authored-by: Hana <andywangsy@gmail.com>
This commit is contained in:
LongYinan 2024-03-20 21:37:08 +08:00 committed by GitHub
parent 693f0ac269
commit 4719caa643
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 815 additions and 664 deletions

View file

@ -37,7 +37,7 @@ fn bench_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
let tsfn = ctx.env.create_threadsafe_function( let tsfn = ctx.env.create_threadsafe_function(
&callback, &callback,
0, 0,
|mut ctx: ThreadSafeCallContext<(usize, Ref<JsBufferValue>)>| { |mut ctx: ThreadsafeCallContext<(usize, Ref<JsBufferValue>)>| {
ctx ctx
.env .env
.create_uint32(ctx.value.0 as u32) .create_uint32(ctx.value.0 as u32)

View file

@ -40,7 +40,7 @@ impl FromStr for LineJoin {
} }
pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> { pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> {
let test_class = env.define_class( let test_class = env.define_class::<bindgen_prelude::Unknown>(
"TestClass", "TestClass",
test_class_constructor, test_class_constructor,
&[ &[

View file

@ -1,4 +1,5 @@
#![allow(clippy::uninlined_format_args)] #![allow(clippy::uninlined_format_args)]
#![allow(deprecated)]
#[macro_use] #[macro_use]
extern crate napi_derive; extern crate napi_derive;

View file

@ -212,6 +212,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, (&'static str, bool, bool)>> = La
("External", ("ExternalObject<{}>", false, false)), ("External", ("ExternalObject<{}>", false, false)),
("unknown", ("unknown", false, false)), ("unknown", ("unknown", false, false)),
("Unknown", ("unknown", false, false)), ("Unknown", ("unknown", false, false)),
("UnknownReturnValue", ("unknown", false, false)),
("JsUnknown", ("unknown", false, false)), ("JsUnknown", ("unknown", false, false)),
("This", ("this", false, false)), ("This", ("this", false, false)),
("Rc", ("{}", false, false)), ("Rc", ("{}", false, false)),
@ -338,6 +339,7 @@ pub fn ty_to_ts_type(
generic_ty, generic_ty,
index == 1 && is_generic_function_type(&rust_ty), index == 1 && is_generic_function_type(&rust_ty),
false, false,
// index == 2 is for ThreadsafeFunction with ErrorStrategy
is_generic_function_type(&rust_ty), is_generic_function_type(&rust_ty),
)) ))
.map(|(mut ty, is_optional)| { .map(|(mut ty, is_optional)| {
@ -346,6 +348,11 @@ pub fn ty_to_ts_type(
} }
(ty, is_optional) (ty, is_optional)
}), }),
// const Generic for `ThreadsafeFunction` generic
syn::GenericArgument::Const(syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(bo),
..
})) => Some((bo.value.to_string(), false)),
_ => None, _ => None,
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -408,15 +415,22 @@ pub fn ty_to_ts_type(
{ {
ts_ty = Some((t, false)); ts_ty = Some((t, false));
} else if rust_ty == TSFN_RUST_TY { } else if rust_ty == TSFN_RUST_TY {
let fatal_tsfn = match args.get(1) { let fatal_tsfn = match args.last() {
Some((arg, _)) => arg == "Fatal", Some((arg, _)) => arg == "false",
_ => false, _ => false,
}; };
let args = args.first().map(|(arg, _)| arg).unwrap(); let fn_args = args.first().map(|(arg, _)| arg).unwrap();
let return_ty = args
.get(1)
.map(|(ty, _)| ty.clone())
.unwrap_or("any".to_owned());
ts_ty = if fatal_tsfn { ts_ty = if fatal_tsfn {
Some((format!("({}) => any", args), false)) Some((format!("({fn_args}) => {return_ty}"), false))
} else { } else {
Some((format!("(err: Error | null, {}) => any", args), false)) Some((
format!("(err: Error | null, {fn_args}) => {return_ty}"),
false,
))
}; };
} else { } else {
// there should be runtime registered type in else // there should be runtime registered type in else

View file

@ -114,11 +114,9 @@ unsafe extern "C" fn complete<T: Task>(
let value = match value_ptr { let value = match value_ptr {
Ok(v) => { Ok(v) => {
let output = unsafe { v.assume_init() }; let output = unsafe { v.assume_init() };
work work.inner_task.resolve(Env::from_raw(env), output)
.inner_task
.resolve(unsafe { Env::from_raw(env) }, output)
} }
Err(e) => work.inner_task.reject(unsafe { Env::from_raw(env) }, e), Err(e) => work.inner_task.reject(Env::from_raw(env), e),
}; };
if status != sys::Status::napi_cancelled && work.status.load(Ordering::Relaxed) != 2 { if status != sys::Status::napi_cancelled && work.status.load(Ordering::Relaxed) != 2 {
match check_status!(status) match check_status!(status)
@ -144,7 +142,7 @@ unsafe extern "C" fn complete<T: Task>(
} }
}; };
} }
if let Err(e) = work.inner_task.finally(unsafe { Env::from_raw(env) }) { if let Err(e) = work.inner_task.finally(Env::from_raw(env)) {
debug_assert!(false, "Panic in Task finally fn: {:?}", e); debug_assert!(false, "Panic in Task finally fn: {:?}", e);
} }
let delete_status = unsafe { sys::napi_delete_async_work(env, napi_async_work) }; let delete_status = unsafe { sys::napi_delete_async_work(env, napi_async_work) };

View file

@ -32,7 +32,7 @@ impl ValidateNapiValue for DateTime<Utc> {
impl ToNapiValue for NaiveDateTime { impl ToNapiValue for NaiveDateTime {
unsafe fn to_napi_value(env: sys::napi_env, val: NaiveDateTime) -> Result<sys::napi_value> { unsafe fn to_napi_value(env: sys::napi_env, val: NaiveDateTime) -> Result<sys::napi_value> {
let mut ptr = std::ptr::null_mut(); let mut ptr = std::ptr::null_mut();
let millis_since_epoch_utc = val.timestamp_millis() as f64; let millis_since_epoch_utc = val.and_utc().timestamp_millis() as f64;
check_status!( check_status!(
unsafe { sys::napi_create_date(env, millis_since_epoch_utc, &mut ptr) }, unsafe { sys::napi_create_date(env, millis_since_epoch_utc, &mut ptr) },
@ -145,11 +145,14 @@ impl FromNapiValue for DateTime<Utc> {
let milliseconds_since_epoch_utc = milliseconds_since_epoch_utc as i64; let milliseconds_since_epoch_utc = milliseconds_since_epoch_utc as i64;
let timestamp_seconds = milliseconds_since_epoch_utc / 1_000; let timestamp_seconds = milliseconds_since_epoch_utc / 1_000;
let naive = NaiveDateTime::from_timestamp_opt( let naive = DateTime::from_timestamp(
timestamp_seconds, timestamp_seconds,
(milliseconds_since_epoch_utc % 1_000 * 1_000_000) as u32, (milliseconds_since_epoch_utc % 1_000 * 1_000_000) as u32,
) )
.ok_or_else(|| Error::new(Status::DateExpected, "Found invalid date".to_owned()))?; .ok_or_else(|| Error::new(Status::DateExpected, "Found invalid date".to_owned()))?;
Ok(DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc)) Ok(DateTime::<Utc>::from_naive_utc_and_offset(
naive.naive_utc(),
Utc,
))
} }
} }

View file

@ -1,7 +1,11 @@
use std::ptr; #![allow(deprecated)]
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue}; use std::{ptr, usize};
use super::{FromNapiValue, ToNapiValue, TypeName, Unknown, ValidateNapiValue};
#[cfg(feature = "napi4")]
use crate::threadsafe_function::ThreadsafeFunction;
pub use crate::JsFunction; pub use crate::JsFunction;
use crate::{check_pending_exception, check_status, sys, Env, NapiRaw, Result, ValueType}; use crate::{check_pending_exception, check_status, sys, Env, NapiRaw, Result, ValueType};
@ -11,11 +15,82 @@ pub trait JsValuesTupleIntoVec {
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>>; fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>>;
} }
impl<T: ToNapiValue> JsValuesTupleIntoVec for T {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> {
Ok(vec![unsafe {
<T as ToNapiValue>::to_napi_value(env, self)?
}])
}
}
pub trait TupleFromSliceValues {
#[allow(clippy::missing_safety_doc)]
unsafe fn from_slice_values(env: sys::napi_env, values: &[sys::napi_value]) -> Result<Self>
where
Self: Sized;
}
macro_rules! impl_tuple_conversion {
($($ident:ident),*) => {
impl<$($ident: ToNapiValue),*> JsValuesTupleIntoVec for ($($ident,)*) {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> {
#[allow(non_snake_case)]
let ($($ident,)*) = self;
Ok(vec![$(unsafe { <$ident as ToNapiValue>::to_napi_value(env, $ident)? }),*])
}
}
impl<$($ident: FromNapiValue),*> TupleFromSliceValues for ($($ident,)*) {
unsafe fn from_slice_values(env: sys::napi_env, values: &[sys::napi_value]) -> $crate::Result<Self> {
#[allow(non_snake_case)]
let [$($ident),*] = values.try_into().map_err(|_| crate::Error::new(
crate::Status::InvalidArg,
"Invalid number of arguments",
))?;
Ok(($(
unsafe { $ident::from_napi_value(env, $ident)?}
,)*))
}
}
};
}
impl_tuple_conversion!(A);
impl_tuple_conversion!(A, B);
impl_tuple_conversion!(A, B, C);
impl_tuple_conversion!(A, B, C, D);
impl_tuple_conversion!(A, B, C, D, E);
impl_tuple_conversion!(A, B, C, D, E, F);
impl_tuple_conversion!(A, B, C, D, E, F, G);
impl_tuple_conversion!(A, B, C, D, E, F, G, H);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
impl_tuple_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
impl_tuple_conversion!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
);
/// A JavaScript function. /// A JavaScript function.
/// It can only live in the scope of a function call. /// It can only live in the scope of a function call.
/// If you want to use it outside the scope of a function call, you can turn it into a reference. /// If you want to use it outside the scope of a function call, you can turn it into a reference.
/// By calling the `create_ref` method. /// By calling the `create_ref` method.
pub struct Function<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> { pub struct Function<'scope, Args: JsValuesTupleIntoVec = Unknown, Return = Unknown> {
pub(crate) env: sys::napi_env, pub(crate) env: sys::napi_env,
pub(crate) value: sys::napi_value, pub(crate) value: sys::napi_value,
pub(crate) _args: std::marker::PhantomData<Args>, pub(crate) _args: std::marker::PhantomData<Args>,
@ -23,9 +98,7 @@ pub struct Function<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> {
_scope: std::marker::PhantomData<&'scope ()>, _scope: std::marker::PhantomData<&'scope ()>,
} }
impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName impl<'scope, Args: JsValuesTupleIntoVec, Return> TypeName for Function<'scope, Args, Return> {
for Function<'scope, Args, Return>
{
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Function" "Function"
} }
@ -35,17 +108,13 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName
} }
} }
impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> NapiRaw impl<'scope, Args: JsValuesTupleIntoVec, Return> NapiRaw for Function<'scope, Args, Return> {
for Function<'scope, Args, Return>
{
unsafe fn raw(&self) -> sys::napi_value { unsafe fn raw(&self) -> sys::napi_value {
self.value self.value
} }
} }
impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> FromNapiValue impl<'scope, Args: JsValuesTupleIntoVec, Return> FromNapiValue for Function<'scope, Args, Return> {
for Function<'scope, Args, Return>
{
unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result<Self> { unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
Ok(Function { Ok(Function {
env, env,
@ -57,11 +126,70 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> FromNapiValue
} }
} }
impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> ValidateNapiValue impl<'scope, Args: JsValuesTupleIntoVec, Return> ValidateNapiValue
for Function<'scope, Args, Return> for Function<'scope, Args, Return>
{ {
} }
impl<'scope, Args: JsValuesTupleIntoVec, Return> Function<'scope, Args, Return> {
/// Get the name of the JavaScript function.
pub fn name(&self) -> Result<String> {
let mut name = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(self.env, self.value, "name\0".as_ptr().cast(), &mut name)
},
"Get function name failed"
)?;
unsafe { String::from_napi_value(self.env, name) }
}
/// Create a reference to the JavaScript function.
pub fn create_ref(&self) -> Result<FunctionRef<Args, Return>> {
let mut reference = ptr::null_mut();
check_status!(
unsafe { sys::napi_create_reference(self.env, self.value, 1, &mut reference) },
"Create reference failed"
)?;
Ok(FunctionRef {
inner: reference,
env: self.env,
_args: std::marker::PhantomData,
_return: std::marker::PhantomData,
})
}
/// Create a new instance of the JavaScript Class.
pub fn new_instance(&self, args: Args) -> Result<Unknown> {
let mut raw_instance = ptr::null_mut();
let mut args = args.into_vec(self.env)?;
check_status!(
unsafe {
sys::napi_new_instance(
self.env,
self.value,
args.len(),
args.as_mut_ptr().cast(),
&mut raw_instance,
)
},
"Create new instance failed"
)?;
unsafe { Unknown::from_napi_value(self.env, raw_instance) }
}
#[cfg(feature = "napi4")]
/// Create a threadsafe function from the JavaScript function.
pub fn build_threadsafe_function(&self) -> ThreadsafeFunctionBuilder<Args, Return> {
ThreadsafeFunctionBuilder {
env: self.env,
value: self.value,
_args: std::marker::PhantomData,
_return: std::marker::PhantomData,
}
}
}
impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope, Args, Return> { impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope, Args, Return> {
/// Call the JavaScript function. /// Call the JavaScript function.
/// `this` in the JavaScript function will be `undefined`. /// `this` in the JavaScript function will be `undefined`.
@ -97,8 +225,7 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope,
let raw_this = unsafe { Context::to_napi_value(self.env, this) }?; let raw_this = unsafe { Context::to_napi_value(self.env, this) }?;
let args_ptr = args.into_vec(self.env)?; let args_ptr = args.into_vec(self.env)?;
let mut raw_return = ptr::null_mut(); let mut raw_return = ptr::null_mut();
check_pending_exception!( check_status!(
self.env,
unsafe { unsafe {
sys::napi_call_function( sys::napi_call_function(
self.env, self.env,
@ -113,35 +240,66 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope,
)?; )?;
unsafe { Return::from_napi_value(self.env, raw_return) } unsafe { Return::from_napi_value(self.env, raw_return) }
} }
}
/// Create a reference to the JavaScript function. pub struct ThreadsafeFunctionBuilder<
pub fn create_ref(&self) -> Result<FunctionRef<Args, Return>> { Args: JsValuesTupleIntoVec,
let mut reference = ptr::null_mut(); Return,
check_status!( const Weak: bool = false,
unsafe { sys::napi_create_reference(self.env, self.value, 1, &mut reference) }, const MaxQueueSize: usize = 0,
"Create reference failed" > {
)?; pub(crate) env: sys::napi_env,
Ok(FunctionRef { pub(crate) value: sys::napi_value,
inner: reference, _args: std::marker::PhantomData<Args>,
_return: std::marker::PhantomData<Return>,
}
impl<
Args: JsValuesTupleIntoVec,
Return: FromNapiValue,
const Weak: bool,
const MaxQueueSize: usize,
> ThreadsafeFunctionBuilder<Args, Return, Weak, MaxQueueSize>
{
pub fn weak<const NewWeak: bool>(
self,
) -> ThreadsafeFunctionBuilder<Args, Return, NewWeak, MaxQueueSize> {
ThreadsafeFunctionBuilder {
env: self.env, env: self.env,
value: self.value,
_args: std::marker::PhantomData, _args: std::marker::PhantomData,
_return: std::marker::PhantomData, _return: std::marker::PhantomData,
}) }
}
pub fn max_queue_size<const NewMaxQueueSize: usize>(
self,
) -> ThreadsafeFunctionBuilder<Args, Return, Weak, NewMaxQueueSize> {
ThreadsafeFunctionBuilder {
env: self.env,
value: self.value,
_args: std::marker::PhantomData,
_return: std::marker::PhantomData,
}
}
pub fn build(self) -> Result<ThreadsafeFunction<Args, Return, false, Weak, MaxQueueSize>> {
unsafe { ThreadsafeFunction::from_napi_value(self.env, self.value) }
} }
} }
/// A reference to a JavaScript function. /// A reference to a JavaScript function.
/// It can be used to outlive the scope of the function. /// It can be used to outlive the scope of the function.
pub struct FunctionRef<Args: JsValuesTupleIntoVec, Return: FromNapiValue> { pub struct FunctionRef<Args: JsValuesTupleIntoVec, Return> {
pub(crate) inner: sys::napi_ref, pub(crate) inner: sys::napi_ref,
pub(crate) env: sys::napi_env, pub(crate) env: sys::napi_env,
_args: std::marker::PhantomData<Args>, _args: std::marker::PhantomData<Args>,
_return: std::marker::PhantomData<Return>, _return: std::marker::PhantomData<Return>,
} }
unsafe impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> Sync for FunctionRef<Args, Return> {} unsafe impl<Args: JsValuesTupleIntoVec, Return> Sync for FunctionRef<Args, Return> {}
impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> FunctionRef<Args, Return> { impl<Args: JsValuesTupleIntoVec, Return> FunctionRef<Args, Return> {
pub fn borrow_back<'scope>(&self, env: &'scope Env) -> Result<Function<'scope, Args, Return>> { pub fn borrow_back<'scope>(&self, env: &'scope Env) -> Result<Function<'scope, Args, Return>> {
let mut value = ptr::null_mut(); let mut value = ptr::null_mut();
check_status!( check_status!(
@ -158,14 +316,14 @@ impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> FunctionRef<Args, Return
} }
} }
impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> Drop for FunctionRef<Args, Return> { impl<Args: JsValuesTupleIntoVec, Return> Drop for FunctionRef<Args, Return> {
fn drop(&mut self) { fn drop(&mut self) {
let status = unsafe { sys::napi_delete_reference(self.env, self.inner) }; let status = unsafe { sys::napi_delete_reference(self.env, self.inner) };
debug_assert_eq!(status, sys::Status::napi_ok, "Drop FunctionRef failed"); debug_assert_eq!(status, sys::Status::napi_ok, "Drop FunctionRef failed");
} }
} }
impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName for FunctionRef<Args, Return> { impl<Args: JsValuesTupleIntoVec, Return> TypeName for FunctionRef<Args, Return> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Function" "Function"
} }
@ -175,9 +333,7 @@ impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName for FunctionRef
} }
} }
impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> FromNapiValue impl<Args: JsValuesTupleIntoVec, Return> FromNapiValue for FunctionRef<Args, Return> {
for FunctionRef<Args, Return>
{
unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result<Self> { unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
let mut reference = ptr::null_mut(); let mut reference = ptr::null_mut();
check_status!( check_status!(
@ -198,6 +354,55 @@ impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> ValidateNapiValue
{ {
} }
pub struct FunctionCallContext<'scope> {
pub(crate) args: &'scope [sys::napi_value],
pub(crate) this: sys::napi_value,
pub(crate) env: &'scope mut Env,
}
impl FunctionCallContext<'_> {
/// Get the number of arguments from the JavaScript function call.
pub fn length(&self) -> usize {
self.args.len()
}
/// Get the first argument from the JavaScript function call.
pub fn first_arg<T: FromNapiValue>(&self) -> Result<T> {
if self.args.is_empty() {
return Err(crate::Error::new(
crate::Status::InvalidArg,
"There is no arguments",
));
}
unsafe { T::from_napi_value(self.env.0, self.args[0]) }
}
/// Get the arguments from the JavaScript function call.
/// The arguments will be converted to a tuple.
/// If the number of arguments is not equal to the number of tuple elements, an error will be returned.
/// example:
/// ```rust
/// let (num, string) = ctx.args::<(u32, String)>()?;
/// ````
pub fn args<Args: TupleFromSliceValues>(&self) -> Result<Args> {
unsafe { Args::from_slice_values(self.env.0, self.args) }
}
/// Get the arguments Vec from the JavaScript function call.
pub fn arguments<T: FromNapiValue>(&self) -> Result<Vec<T>> {
self
.args
.iter()
.map(|arg| unsafe { <T as FromNapiValue>::from_napi_value(self.env.0, *arg) })
.collect::<Result<Vec<T>>>()
}
/// Get the `this` from the JavaScript function call.
pub fn this<This: FromNapiValue>(&self) -> Result<This> {
unsafe { This::from_napi_value(self.env.0, self.this) }
}
}
macro_rules! impl_call_apply { macro_rules! impl_call_apply {
($fn_call_name:ident, $fn_apply_name:ident, $($ident:ident),*) => { ($fn_call_name:ident, $fn_apply_name:ident, $($ident:ident),*) => {
#[allow(non_snake_case, clippy::too_many_arguments)] #[allow(non_snake_case, clippy::too_many_arguments)]
@ -205,7 +410,7 @@ macro_rules! impl_call_apply {
&self, &self,
$($ident: $ident),* $($ident: $ident),*
) -> Result<Return> { ) -> Result<Return> {
let raw_this = unsafe { Env::from_raw(self.0.env) } let raw_this = Env::from_raw(self.0.env)
.get_undefined() .get_undefined()
.map(|u| unsafe { u.raw() })?; .map(|u| unsafe { u.raw() })?;
@ -284,7 +489,7 @@ impl JsFunction {
} }
pub fn call0<Return: FromNapiValue>(&self) -> Result<Return> { pub fn call0<Return: FromNapiValue>(&self) -> Result<Return> {
let raw_this = unsafe { Env::from_raw(self.0.env) } let raw_this = Env::from_raw(self.0.env)
.get_undefined() .get_undefined()
.map(|u| unsafe { u.raw() })?; .map(|u| unsafe { u.raw() })?;

View file

@ -3,7 +3,7 @@ use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering}; use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering};
use super::{FromNapiValue, ToNapiValue, TypeName}; use super::{FromNapiValue, ToNapiValue, TypeName, Unknown};
use crate::{ use crate::{
async_work, check_status, sys, Env, Error, JsError, JsObject, NapiValue, Status, Task, async_work, check_status, sys, Env, Error, JsError, JsObject, NapiValue, Status, Task,
}; };
@ -65,7 +65,7 @@ impl FromNapiValue for AbortSignal {
raw_deferred: raw_promise.clone(), raw_deferred: raw_promise.clone(),
status: task_status.clone(), status: task_status.clone(),
}; };
let js_env = unsafe { Env::from_raw(env) }; let js_env = Env::from_raw(env);
check_status!(unsafe { check_status!(unsafe {
sys::napi_wrap( sys::napi_wrap(
env, env,
@ -76,7 +76,10 @@ impl FromNapiValue for AbortSignal {
ptr::null_mut(), ptr::null_mut(),
) )
})?; })?;
signal.set_named_property("onabort", js_env.create_function("onabort", on_abort)?)?; signal.set_named_property(
"onabort",
js_env.create_function::<Unknown, Unknown>("onabort", on_abort)?,
)?;
Ok(AbortSignal { Ok(AbortSignal {
raw_work: async_work_inner, raw_work: async_work_inner,
raw_deferred: raw_promise, raw_deferred: raw_promise,

View file

@ -35,7 +35,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked<T: ObjectFinalize>(
_finalize_hint: *mut c_void, _finalize_hint: *mut c_void,
) { ) {
let data: Box<T> = unsafe { Box::from_raw(finalize_data.cast()) }; let data: Box<T> = unsafe { Box::from_raw(finalize_data.cast()) };
if let Err(err) = data.finalize(unsafe { Env::from_raw(env) }) { if let Err(err) = data.finalize(Env::from_raw(env)) {
let e: JsError = err.into(); let e: JsError = err.into();
unsafe { e.throw_into(env) }; unsafe { e.throw_into(env) };
return; return;

View file

@ -7,9 +7,26 @@ use std::mem;
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use std::ptr; use std::ptr;
use crate::bindgen_runtime::FromNapiValue; #[cfg(feature = "serde-json")]
use serde::de::DeserializeOwned;
#[cfg(feature = "serde-json")]
use serde::Serialize;
#[cfg(feature = "napi8")]
use crate::async_cleanup_hook::AsyncCleanupHook;
#[cfg(feature = "napi5")]
use crate::bindgen_runtime::FunctionCallContext;
#[cfg(feature = "napi4")] #[cfg(feature = "napi4")]
use crate::bindgen_runtime::ToNapiValue; use crate::bindgen_runtime::ToNapiValue;
use crate::bindgen_runtime::{FromNapiValue, Function, JsValuesTupleIntoVec, Unknown};
#[cfg(feature = "napi3")]
use crate::cleanup_env::{CleanupEnvHook, CleanupEnvHookData};
#[cfg(feature = "serde-json")]
use crate::js_values::{De, Ser};
#[cfg(feature = "napi4")]
use crate::threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction};
#[cfg(feature = "napi3")]
use crate::JsError;
use crate::{ use crate::{
async_work::{self, AsyncWorkPromise}, async_work::{self, AsyncWorkPromise},
check_status, check_status,
@ -19,21 +36,6 @@ use crate::{
Error, ExtendedErrorInfo, NodeVersion, Result, Status, ValueType, Error, ExtendedErrorInfo, NodeVersion, Result, Status, ValueType,
}; };
#[cfg(feature = "napi8")]
use crate::async_cleanup_hook::AsyncCleanupHook;
#[cfg(feature = "napi3")]
use crate::cleanup_env::{CleanupEnvHook, CleanupEnvHookData};
#[cfg(feature = "serde-json")]
use crate::js_values::{De, Ser};
#[cfg(feature = "napi4")]
use crate::threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction};
#[cfg(feature = "napi3")]
use crate::JsError;
#[cfg(feature = "serde-json")]
use serde::de::DeserializeOwned;
#[cfg(feature = "serde-json")]
use serde::Serialize;
pub type Callback = unsafe extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value; pub type Callback = unsafe extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys::napi_value;
pub(crate) static EMPTY_VEC: Vec<u8> = vec![]; pub(crate) static EMPTY_VEC: Vec<u8> = vec![];
@ -58,7 +60,7 @@ impl From<sys::napi_env> for Env {
impl Env { impl Env {
#[allow(clippy::missing_safety_doc)] #[allow(clippy::missing_safety_doc)]
pub unsafe fn from_raw(env: sys::napi_env) -> Self { pub fn from_raw(env: sys::napi_env) -> Self {
Env(env) Env(env)
} }
@ -189,12 +191,7 @@ impl Env {
pub fn create_string_latin1(&self, chars: &[u8]) -> Result<JsString> { pub fn create_string_latin1(&self, chars: &[u8]) -> Result<JsString> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
check_status!(unsafe { check_status!(unsafe {
sys::napi_create_string_latin1( sys::napi_create_string_latin1(self.0, chars.as_ptr().cast(), chars.len(), &mut raw_value)
self.0,
chars.as_ptr() as *const _,
chars.len(),
&mut raw_value,
)
})?; })?;
Ok(unsafe { JsString::from_raw_unchecked(self.0, raw_value) }) Ok(unsafe { JsString::from_raw_unchecked(self.0, raw_value) })
} }
@ -570,14 +567,17 @@ impl Env {
/// The newly created function is not automatically visible from script after this call. /// The newly created function is not automatically visible from script after this call.
/// ///
/// Instead, a property must be explicitly set on any object that is visible to JavaScript, in order for the function to be accessible from script. /// Instead, a property must be explicitly set on any object that is visible to JavaScript, in order for the function to be accessible from script.
pub fn create_function(&self, name: &str, callback: Callback) -> Result<JsFunction> { pub fn create_function<Args: JsValuesTupleIntoVec, Return>(
&self,
name: &str,
callback: Callback,
) -> Result<Function<Args, Return>> {
let mut raw_result = ptr::null_mut(); let mut raw_result = ptr::null_mut();
let len = name.len(); let len = name.len();
let name = CString::new(name)?;
check_status!(unsafe { check_status!(unsafe {
sys::napi_create_function( sys::napi_create_function(
self.0, self.0,
name.as_ptr(), name.as_ptr().cast(),
len, len,
Some(callback), Some(callback),
ptr::null_mut(), ptr::null_mut(),
@ -585,26 +585,29 @@ impl Env {
) )
})?; })?;
Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) unsafe { Function::<Args, Return>::from_napi_value(self.0, raw_result) }
} }
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
pub fn create_function_from_closure<R, F>(&self, name: &str, callback: F) -> Result<JsFunction> pub fn create_function_from_closure<Args: JsValuesTupleIntoVec, Return, F>(
&self,
name: &str,
callback: F,
) -> Result<Function<Args, Return>>
where where
F: 'static + Fn(crate::CallContext<'_>) -> Result<R>, Return: ToNapiValue,
R: ToNapiValue, F: 'static + Fn(FunctionCallContext) -> Result<Return>,
{ {
let closure_data_ptr = Box::into_raw(Box::new(callback)); let closure_data_ptr = Box::into_raw(Box::new(callback));
let mut raw_result = ptr::null_mut(); let mut raw_result = ptr::null_mut();
let len = name.len(); let len = name.len();
let name = CString::new(name)?;
check_status!(unsafe { check_status!(unsafe {
sys::napi_create_function( sys::napi_create_function(
self.0, self.0,
name.as_ptr(), name.as_ptr().cast(),
len, len,
Some(trampoline::<R, F>), Some(trampoline::<Return, F>),
closure_data_ptr.cast(), // We let it borrow the data here closure_data_ptr.cast(), // We let it borrow the data here
&mut raw_result, &mut raw_result,
) )
@ -629,7 +632,7 @@ impl Env {
) )
})?; })?;
Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) unsafe { Function::from_napi_value(self.0, raw_result) }
} }
/// This API retrieves a napi_extended_error_info structure with information about the last error that occurred. /// This API retrieves a napi_extended_error_info structure with information about the last error that occurred.
@ -740,12 +743,12 @@ impl Env {
} }
/// Create JavaScript class /// Create JavaScript class
pub fn define_class( pub fn define_class<Args: JsValuesTupleIntoVec>(
&self, &self,
name: &str, name: &str,
constructor_cb: Callback, constructor_cb: Callback,
properties: &[Property], properties: &[Property],
) -> Result<JsFunction> { ) -> Result<Function<Args, Unknown>> {
let mut raw_result = ptr::null_mut(); let mut raw_result = ptr::null_mut();
let raw_properties = properties let raw_properties = properties
.iter() .iter()
@ -755,7 +758,7 @@ impl Env {
check_status!(unsafe { check_status!(unsafe {
sys::napi_define_class( sys::napi_define_class(
self.0, self.0,
c_name.as_ptr() as *const c_char, c_name.as_ptr().cast(),
name.len(), name.len(),
Some(constructor_cb), Some(constructor_cb),
ptr::null_mut(), ptr::null_mut(),
@ -765,7 +768,7 @@ impl Env {
) )
})?; })?;
Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) unsafe { Function::from_napi_value(self.0, raw_result) }
} }
#[allow(clippy::needless_pass_by_ref_mut)] #[allow(clippy::needless_pass_by_ref_mut)]
@ -1055,17 +1058,22 @@ impl Env {
} }
#[cfg(feature = "napi4")] #[cfg(feature = "napi4")]
#[deprecated(
since = "2.17.0",
note = "Please use `Function::build_threadsafe_function` instead"
)]
#[allow(deprecated)]
pub fn create_threadsafe_function< pub fn create_threadsafe_function<
T: Send, T: Send,
V: ToNapiValue, V: ToNapiValue,
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>, R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
>( >(
&self, &self,
func: &JsFunction, func: &JsFunction,
max_queue_size: usize, _max_queue_size: usize,
callback: R, callback: R,
) -> Result<ThreadsafeFunction<T>> { ) -> Result<ThreadsafeFunction<T>> {
ThreadsafeFunction::create(self.0, func.0.value, max_queue_size, callback) ThreadsafeFunction::create(self.0, func.0.value, callback)
} }
#[cfg(all(feature = "tokio_rt", feature = "napi4"))] #[cfg(all(feature = "tokio_rt", feature = "napi4"))]
@ -1403,7 +1411,7 @@ unsafe extern "C" fn set_instance_finalize_callback<T, Hint, F>(
{ {
let (value, callback) = unsafe { *Box::from_raw(finalize_data as *mut (TaggedObject<T>, F)) }; let (value, callback) = unsafe { *Box::from_raw(finalize_data as *mut (TaggedObject<T>, F)) };
let hint = unsafe { *Box::from_raw(finalize_hint as *mut Hint) }; let hint = unsafe { *Box::from_raw(finalize_hint as *mut Hint) };
let env = unsafe { Env::from_raw(raw_env) }; let env = Env::from_raw(raw_env);
callback(FinalizeContext { callback(FinalizeContext {
value: value.object.unwrap(), value: value.object.unwrap(),
hint, hint,
@ -1425,7 +1433,7 @@ unsafe extern "C" fn raw_finalize_with_custom_callback<Hint, Finalize>(
Finalize: FnOnce(Hint, Env), Finalize: FnOnce(Hint, Env),
{ {
let (hint, callback) = unsafe { *Box::from_raw(finalize_hint as *mut (Hint, Finalize)) }; let (hint, callback) = unsafe { *Box::from_raw(finalize_hint as *mut (Hint, Finalize)) };
callback(hint, unsafe { Env::from_raw(env) }); callback(hint, Env::from_raw(env));
} }
#[cfg(feature = "napi8")] #[cfg(feature = "napi8")]
@ -1449,22 +1457,20 @@ unsafe extern "C" fn async_finalize<Arg, F>(
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
pub(crate) unsafe extern "C" fn trampoline< pub(crate) unsafe extern "C" fn trampoline<
R: ToNapiValue, Return: ToNapiValue,
F: Fn(crate::CallContext) -> Result<R>, F: Fn(FunctionCallContext) -> Result<Return>,
>( >(
raw_env: sys::napi_env, raw_env: sys::napi_env,
cb_info: sys::napi_callback_info, cb_info: sys::napi_callback_info,
) -> sys::napi_value { ) -> sys::napi_value {
use crate::CallContext; // Fast path for 4 arguments or less.
let mut argc = 4;
let mut raw_args = Vec::with_capacity(4);
let mut raw_this = ptr::null_mut();
let mut closure_data_ptr = ptr::null_mut();
let (raw_this, raw_args, closure_data_ptr, argc) = { check_status!(
// Fast path for 4 arguments or less. unsafe {
let mut argc = 4;
let mut raw_args = Vec::with_capacity(4);
let mut raw_this = ptr::null_mut();
let mut closure_data_ptr = ptr::null_mut();
let status = unsafe {
sys::napi_get_cb_info( sys::napi_get_cb_info(
raw_env, raw_env,
cb_info, cb_info,
@ -1473,45 +1479,45 @@ pub(crate) unsafe extern "C" fn trampoline<
&mut raw_this, &mut raw_this,
&mut closure_data_ptr, &mut closure_data_ptr,
) )
}; },
debug_assert!( "napi_get_cb_info failed"
Status::from(status) == Status::Ok, )
"napi_get_cb_info failed" .and_then(|_| {
);
// Arguments length greater than 4, resize the vector. // Arguments length greater than 4, resize the vector.
if argc > 4 { if argc > 4 {
raw_args = vec![ptr::null_mut(); argc]; raw_args = vec![ptr::null_mut(); argc];
let status = unsafe { check_status!(
sys::napi_get_cb_info( unsafe {
raw_env, sys::napi_get_cb_info(
cb_info, raw_env,
&mut argc, cb_info,
raw_args.as_mut_ptr(), &mut argc,
&mut raw_this, raw_args.as_mut_ptr(),
&mut closure_data_ptr, &mut raw_this,
) &mut closure_data_ptr,
}; )
debug_assert!( },
Status::from(status) == Status::Ok,
"napi_get_cb_info failed" "napi_get_cb_info failed"
); )?;
} else { } else {
unsafe { raw_args.set_len(argc) }; unsafe { raw_args.set_len(argc) };
} }
Ok((raw_this, raw_args, closure_data_ptr, argc))
(raw_this, raw_args, closure_data_ptr, argc) })
}; .and_then(|(raw_this, raw_args, closure_data_ptr, _argc)| {
let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) });
let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); let mut env = Env::from_raw(raw_env);
let mut env = unsafe { Env::from_raw(raw_env) }; closure(FunctionCallContext {
let call_context = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc); env: &mut env,
closure(call_context) this: raw_this,
.and_then(|ret: R| unsafe { <R as ToNapiValue>::to_napi_value(env.0, ret) }) args: raw_args.as_slice(),
.unwrap_or_else(|e| {
unsafe { JsError::from(e).throw_into(raw_env) };
ptr::null_mut()
}) })
})
.and_then(|ret| unsafe { <Return as ToNapiValue>::to_napi_value(raw_env, ret) })
.unwrap_or_else(|e| {
unsafe { JsError::from(e).throw_into(raw_env) };
ptr::null_mut()
})
} }
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
@ -1551,7 +1557,7 @@ pub(crate) unsafe extern "C" fn trampoline_setter<
}; };
let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) });
let env = unsafe { Env::from_raw(raw_env) }; let env = Env::from_raw(raw_env);
raw_args raw_args
.first() .first()
.ok_or_else(|| Error::new(Status::InvalidArg, "Missing argument in property setter")) .ok_or_else(|| Error::new(Status::InvalidArg, "Missing argument in property setter"))
@ -1602,7 +1608,7 @@ pub(crate) unsafe extern "C" fn trampoline_getter<
}; };
let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) });
let env = unsafe { Env::from_raw(raw_env) }; let env = Env::from_raw(raw_env);
closure(env, unsafe { closure(env, unsafe {
crate::bindgen_runtime::Object::from_raw_unchecked(raw_env, raw_this) crate::bindgen_runtime::Object::from_raw_unchecked(raw_env, raw_this)
}) })

View file

@ -20,7 +20,7 @@ pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
/// Represent `JsError`. /// Represent `JsError`.
/// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you. /// Return this Error in `js_function`, **napi-rs** will throw it as `JsError` for you.
/// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)` /// If you want throw it as `TypeError` or `RangeError`, you can use `JsTypeError/JsRangeError::from(Error).throw_into(env)`
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct Error<S: AsRef<str> = Status> { pub struct Error<S: AsRef<str> = Status> {
pub status: S, pub status: S,
pub reason: String, pub reason: String,
@ -28,6 +28,17 @@ pub struct Error<S: AsRef<str> = Status> {
pub(crate) maybe_raw: sys::napi_ref, pub(crate) maybe_raw: sys::napi_ref,
} }
impl<S: AsRef<str>> std::fmt::Debug for Error<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Error {{ status: {:?}, reason: {:?} }}",
self.status.as_ref(),
self.reason
)
}
}
impl<S: AsRef<str>> ToNapiValue for Error<S> { impl<S: AsRef<str>> ToNapiValue for Error<S> {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
if val.maybe_raw.is_null() { if val.maybe_raw.is_null() {
@ -358,7 +369,13 @@ macro_rules! check_status_and_type {
let value_type = $crate::type_of!($env, $val)?; let value_type = $crate::type_of!($env, $val)?;
let error_msg = match value_type { let error_msg = match value_type {
ValueType::Function => { ValueType::Function => {
let function_name = unsafe { JsFunction::from_raw_unchecked($env, $val).name()? }; let function_name = unsafe {
$crate::bindgen_prelude::Function::<
$crate::bindgen_prelude::Unknown,
$crate::bindgen_prelude::Unknown,
>::from_napi_value($env, $val)?
.name()?
};
format!( format!(
$msg, $msg,
format!( format!(

View file

@ -23,7 +23,7 @@ struct DeferredTrace(sys::napi_ref);
#[cfg(feature = "deferred_trace")] #[cfg(feature = "deferred_trace")]
impl DeferredTrace { impl DeferredTrace {
fn new(raw_env: sys::napi_env) -> Result<Self> { fn new(raw_env: sys::napi_env) -> Result<Self> {
let env = unsafe { Env::from_raw(raw_env) }; let env = Env::from_raw(raw_env);
let reason = env.create_string("none").unwrap(); let reason = env.create_string("none").unwrap();
let mut js_error = ptr::null_mut(); let mut js_error = ptr::null_mut();
@ -42,7 +42,7 @@ impl DeferredTrace {
} }
fn into_rejected(self, raw_env: sys::napi_env, err: Error) -> Result<sys::napi_value> { fn into_rejected(self, raw_env: sys::napi_env, err: Error) -> Result<sys::napi_value> {
let env = unsafe { Env::from_raw(raw_env) }; let env = Env::from_raw(raw_env);
let mut raw = ptr::null_mut(); let mut raw = ptr::null_mut();
check_status!( check_status!(
unsafe { sys::napi_get_reference_value(raw_env, self.0, &mut raw) }, unsafe { sys::napi_get_reference_value(raw_env, self.0, &mut raw) },
@ -210,7 +210,7 @@ extern "C" fn napi_resolve_deferred<Data: ToNapiValue, Resolver: FnOnce(Env) ->
let deferred_data: Box<DeferredData<Data, Resolver>> = unsafe { Box::from_raw(data.cast()) }; let deferred_data: Box<DeferredData<Data, Resolver>> = unsafe { Box::from_raw(data.cast()) };
let result = deferred_data let result = deferred_data
.resolver .resolver
.and_then(|resolver| resolver(unsafe { Env::from_raw(env) })) .and_then(|resolver| resolver(Env::from_raw(env)))
.and_then(|res| unsafe { ToNapiValue::to_napi_value(env, res) }); .and_then(|res| unsafe { ToNapiValue::to_napi_value(env, res) });
if let Err(e) = result.and_then(|res| { if let Err(e) = result.and_then(|res| {

View file

@ -4,12 +4,13 @@ use super::Value;
#[cfg(feature = "napi4")] #[cfg(feature = "napi4")]
use crate::{ use crate::{
bindgen_runtime::ToNapiValue, bindgen_runtime::ToNapiValue,
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction}, threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction},
}; };
use crate::{bindgen_runtime::TypeName, JsString}; use crate::{bindgen_runtime::TypeName, JsString};
use crate::{check_pending_exception, ValueType}; use crate::{check_pending_exception, ValueType};
use crate::{sys, Env, Error, JsObject, JsUnknown, NapiRaw, NapiValue, Result, Status}; use crate::{sys, Env, Error, JsObject, JsUnknown, NapiRaw, NapiValue, Result, Status};
#[deprecated(since = "2.17.0", note = "Please use `Function` instead")]
pub struct JsFunction(pub(crate) Value); pub struct JsFunction(pub(crate) Value);
impl TypeName for JsFunction { impl TypeName for JsFunction {
@ -45,7 +46,7 @@ impl JsFunction {
let raw_this = this let raw_this = this
.map(|v| unsafe { v.raw() }) .map(|v| unsafe { v.raw() })
.or_else(|| { .or_else(|| {
unsafe { Env::from_raw(self.0.env) } Env::from_raw(self.0.env)
.get_undefined() .get_undefined()
.ok() .ok()
.map(|u| unsafe { u.raw() }) .map(|u| unsafe { u.raw() })
@ -76,7 +77,7 @@ impl JsFunction {
let raw_this = this let raw_this = this
.map(|v| unsafe { v.raw() }) .map(|v| unsafe { v.raw() })
.or_else(|| { .or_else(|| {
unsafe { Env::from_raw(self.0.env) } Env::from_raw(self.0.env)
.get_undefined() .get_undefined()
.ok() .ok()
.map(|u| unsafe { u.raw() }) .map(|u| unsafe { u.raw() })
@ -138,17 +139,24 @@ impl JsFunction {
} }
#[cfg(feature = "napi4")] #[cfg(feature = "napi4")]
pub fn create_threadsafe_function<T, V, F, ES>( pub fn create_threadsafe_function<
T,
V,
Return,
F,
const ES: bool,
const Weak: bool,
const MaxQueueSize: usize,
>(
&self, &self,
max_queue_size: usize,
callback: F, callback: F,
) -> Result<ThreadsafeFunction<T, ES>> ) -> Result<ThreadsafeFunction<T, Return, ES, Weak, MaxQueueSize>>
where where
T: 'static, T: 'static,
Return: crate::bindgen_runtime::FromNapiValue,
V: ToNapiValue, V: ToNapiValue,
F: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>, F: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
ES: crate::threadsafe_function::ErrorStrategy::T,
{ {
ThreadsafeFunction::create(self.0.env, self.0.value, max_queue_size, callback) ThreadsafeFunction::create(self.0.env, self.0.value, callback)
} }
} }

View file

@ -1,7 +1,8 @@
use std::convert::TryInto;
use super::*; use super::*;
use crate::{bindgen_runtime::FromNapiValue, Env}; use crate::{
bindgen_runtime::{FromNapiValue, Function},
threadsafe_function::UnknownReturnValue,
};
pub struct JsGlobal(pub(crate) Value); pub struct JsGlobal(pub(crate) Value);
@ -21,56 +22,41 @@ impl FromNapiValue for JSON {
impl JSON { impl JSON {
pub fn stringify<V: NapiRaw>(&self, value: V) -> Result<std::string::String> { pub fn stringify<V: NapiRaw>(&self, value: V) -> Result<std::string::String> {
let func: JsFunction = self.get_named_property_unchecked("stringify")?; let func: Function<V, std::string::String> = self.get_named_property_unchecked("stringify")?;
let result = func func.call(value)
.call(None, &[value])
.map(|ret| unsafe { ret.cast::<JsString>() })?;
result.into_utf8()?.as_str().map(|s| s.to_owned())
} }
} }
impl JsGlobal { impl JsGlobal {
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> { pub fn set_interval(
let func: JsFunction = self.get_named_property_unchecked("setInterval")?; &self,
func handler: Function<(), UnknownReturnValue>,
.call( interval: f64,
None, ) -> Result<JsTimeout> {
&[ let func: Function<(Function<(), UnknownReturnValue>, f64), JsTimeout> =
handler.into_unknown(), self.get_named_property_unchecked("setInterval")?;
unsafe { Env::from_raw(self.0.env) } func.call((handler, interval))
.create_double(interval)?
.into_unknown(),
],
)
.and_then(|ret| ret.try_into())
} }
pub fn clear_interval(&self, timer: JsTimeout) -> Result<JsUndefined> { pub fn clear_interval(&self, timer: JsTimeout) -> Result<JsUndefined> {
let func: JsFunction = self.get_named_property_unchecked("clearInterval")?; let func: Function<JsTimeout, JsUndefined> =
func self.get_named_property_unchecked("clearInterval")?;
.call(None, &[timer.into_unknown()]) func.call(timer)
.and_then(|ret| ret.try_into())
} }
pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> { pub fn set_timeout(
let func: JsFunction = self.get_named_property_unchecked("setTimeout")?; &self,
func handler: Function<(), UnknownReturnValue>,
.call( interval: f64,
None, ) -> Result<JsTimeout> {
&[ let func: Function<(Function<(), UnknownReturnValue>, f64), JsTimeout> =
handler.into_unknown(), self.get_named_property_unchecked("setTimeout")?;
unsafe { Env::from_raw(self.0.env) } func.call((handler, interval))
.create_double(interval)?
.into_unknown(),
],
)
.and_then(|ret| ret.try_into())
} }
pub fn clear_timeout(&self, timer: JsTimeout) -> Result<JsUndefined> { pub fn clear_timeout(&self, timer: JsTimeout) -> Result<JsUndefined> {
let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?; let func: Function<JsTimeout, JsUndefined> =
func self.get_named_property_unchecked("clearTimeout")?;
.call(None, &[timer.into_unknown()]) func.call(timer)
.and_then(|ret| ret.try_into())
} }
} }

View file

@ -1,3 +1,5 @@
#![allow(deprecated)]
use std::convert::TryFrom; use std::convert::TryFrom;
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
use std::ffi::c_void; use std::ffi::c_void;

View file

@ -76,7 +76,7 @@ unsafe extern "C" fn finalize_callback<T, Hint, F>(
let (value, callback, raw_ref) = let (value, callback, raw_ref) =
unsafe { *Box::from_raw(finalize_data as *mut (T, F, sys::napi_ref)) }; unsafe { *Box::from_raw(finalize_data as *mut (T, F, sys::napi_ref)) };
let hint = unsafe { *Box::from_raw(finalize_hint as *mut Hint) }; let hint = unsafe { *Box::from_raw(finalize_hint as *mut Hint) };
let env = unsafe { Env::from_raw(raw_env) }; let env = Env::from_raw(raw_env);
callback(FinalizeContext { env, value, hint }); callback(FinalizeContext { env, value, hint });
if !raw_ref.is_null() { if !raw_ref.is_null() {
let status = unsafe { sys::napi_delete_reference(raw_env, raw_ref) }; let status = unsafe { sys::napi_delete_reference(raw_env, raw_ref) };

View file

@ -308,7 +308,7 @@ impl ser::SerializeSeq for SeqSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.array.0.env) }; let env = Env::from_raw(self.array.0.env);
self.array.set_element( self.array.set_element(
self.current_index as _, self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?), JsUnknown(value.serialize(Ser::new(&env))?),
@ -331,7 +331,7 @@ impl ser::SerializeTuple for SeqSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.array.0.env) }; let env = Env::from_raw(self.array.0.env);
self.array.set_element( self.array.set_element(
self.current_index as _, self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?), JsUnknown(value.serialize(Ser::new(&env))?),
@ -354,7 +354,7 @@ impl ser::SerializeTupleStruct for SeqSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.array.0.env) }; let env = Env::from_raw(self.array.0.env);
self.array.set_element( self.array.set_element(
self.current_index as _, self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?), JsUnknown(value.serialize(Ser::new(&env))?),
@ -377,7 +377,7 @@ impl ser::SerializeTupleVariant for SeqSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.array.0.env) }; let env = Env::from_raw(self.array.0.env);
self.array.set_element( self.array.set_element(
self.current_index as _, self.current_index as _,
JsUnknown(value.serialize(Ser::new(&env))?), JsUnknown(value.serialize(Ser::new(&env))?),
@ -405,7 +405,7 @@ impl ser::SerializeMap for MapSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.obj.0.env) }; let env = Env::from_raw(self.obj.0.env);
self.key = JsString(key.serialize(Ser::new(&env))?); self.key = JsString(key.serialize(Ser::new(&env))?);
Ok(()) Ok(())
} }
@ -414,7 +414,7 @@ impl ser::SerializeMap for MapSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.obj.0.env) }; let env = Env::from_raw(self.obj.0.env);
self.obj.set_property( self.obj.set_property(
JsString(Value { JsString(Value {
env: self.key.0.env, env: self.key.0.env,
@ -435,7 +435,7 @@ impl ser::SerializeMap for MapSerializer {
K: Serialize, K: Serialize,
V: Serialize, V: Serialize,
{ {
let env = unsafe { Env::from_raw(self.obj.0.env) }; let env = Env::from_raw(self.obj.0.env);
self.obj.set_property( self.obj.set_property(
JsString(key.serialize(Ser::new(&env))?), JsString(key.serialize(Ser::new(&env))?),
JsUnknown(value.serialize(Ser::new(&env))?), JsUnknown(value.serialize(Ser::new(&env))?),
@ -461,7 +461,7 @@ impl ser::SerializeStruct for StructSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.obj.0.env) }; let env = Env::from_raw(self.obj.0.env);
self self
.obj .obj
.set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?;
@ -482,7 +482,7 @@ impl ser::SerializeStructVariant for StructSerializer {
where where
T: Serialize, T: Serialize,
{ {
let env = unsafe { Env::from_raw(self.obj.0.env) }; let env = Env::from_raw(self.obj.0.env);
self self
.obj .obj
.set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?;

View file

@ -13,13 +13,15 @@ pub trait Task: Send + Sized {
/// Into this method if `compute` return `Ok` /// Into this method if `compute` return `Ok`
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue>; fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue>;
#[allow(unused_variables)]
/// Into this method if `compute` return `Err` /// Into this method if `compute` return `Err`
fn reject(&mut self, _env: Env, err: Error) -> Result<Self::JsValue> { fn reject(&mut self, env: Env, err: Error) -> Result<Self::JsValue> {
Err(err) Err(err)
} }
// after resolve or reject #[allow(unused_variables)]
fn finally(&mut self, _env: Env) -> Result<()> { /// after resolve or reject
fn finally(&mut self, env: Env) -> Result<()> {
Ok(()) Ok(())
} }
} }

View file

@ -1,21 +1,26 @@
#![allow(clippy::single_component_path_imports)] #![allow(clippy::single_component_path_imports)]
use std::convert::Into; use std::convert::Into;
use std::ffi::CString;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::ptr::{self, null_mut}; use std::ptr::{self, null_mut};
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use std::sync::{
use std::sync::{Arc, RwLock, RwLockWriteGuard, Weak}; self,
atomic::{AtomicBool, AtomicPtr, Ordering},
Arc, RwLock, RwLockWriteGuard,
};
use crate::bindgen_runtime::{ use crate::bindgen_runtime::{
FromNapiValue, JsValuesTupleIntoVec, ToNapiValue, TypeName, ValidateNapiValue, FromNapiValue, JsValuesTupleIntoVec, ToNapiValue, TypeName, Unknown, ValidateNapiValue,
}; };
use crate::{check_status, sys, Env, JsError, JsUnknown, Result, Status}; use crate::{check_status, sys, Env, Error, JsError, Result, Status};
#[deprecated(since = "2.17.0", note = "Please use `ThreadsafeFunction` instead")]
pub type ThreadSafeCallContext<T> = ThreadsafeCallContext<T>;
/// ThreadSafeFunction Context object /// ThreadSafeFunction Context object
/// the `value` is the value passed to `call` method /// the `value` is the value passed to `call` method
pub struct ThreadSafeCallContext<T: 'static> { pub struct ThreadsafeCallContext<T: 'static> {
pub env: Env, pub env: Env,
pub value: T, pub value: T,
} }
@ -36,68 +41,6 @@ impl From<ThreadsafeFunctionCallMode> for sys::napi_threadsafe_function_call_mod
} }
} }
type_level_enum! {
/// Type-level `enum` to express how to feed [`ThreadsafeFunction`] errors to
/// the inner [`JsFunction`].
///
/// ### Context
///
/// For callbacks that expect a `Result`-like kind of input, the convention is
/// to have the callback take an `error` parameter as its first parameter.
///
/// This way receiving a `Result<Args…>` can be modelled as follows:
///
/// - In case of `Err(error)`, feed that `error` entity as the first parameter
/// of the callback;
///
/// - Otherwise (in case of `Ok(_)`), feed `null` instead.
///
/// In pseudo-code:
///
/// ```rust,ignore
/// match result_args {
/// Ok(args) => {
/// let js_null = /* … */;
/// callback.call(
/// // this
/// None,
/// // args…
/// &iter::once(js_null).chain(args).collect::<Vec<_>>(),
/// )
/// },
/// Err(err) => callback.call(None, &[JsError::from(err)]),
/// }
/// ```
///
/// **Note that the `Err` case can stem from a failed conversion from native
/// values to js values when calling the callback!**
///
/// That's why:
///
/// > **[This][`ErrorStrategy::CalleeHandled`] is the default error strategy**.
///
/// In order to opt-out of it, [`ThreadsafeFunction`] has an optional second
/// generic parameter (of "kind" [`ErrorStrategy::T`]) that defines whether
/// this behavior ([`ErrorStrategy::CalleeHandled`]) or a non-`Result` one
/// ([`ErrorStrategy::Fatal`]) is desired.
pub enum ErrorStrategy {
/// Input errors (including conversion errors) are left for the callee to
/// handle:
///
/// The callee receives an extra `error` parameter (the first one), which is
/// `null` if no error occurred, and the error payload otherwise.
CalleeHandled,
/// Input errors (including conversion errors) are deemed fatal:
///
/// they can thus cause a `panic!` or abort the process.
///
/// The callee thus is not expected to have to deal with [that extra `error`
/// parameter][CalleeHandled], which is thus not added.
Fatal,
}
}
struct ThreadsafeFunctionHandle { struct ThreadsafeFunctionHandle {
raw: AtomicPtr<sys::napi_threadsafe_function__>, raw: AtomicPtr<sys::napi_threadsafe_function__>,
aborted: RwLock<bool>, aborted: RwLock<bool>,
@ -178,10 +121,10 @@ enum ThreadsafeFunctionCallVariant {
WithCallback, WithCallback,
} }
struct ThreadsafeFunctionCallJsBackData<T> { struct ThreadsafeFunctionCallJsBackData<T, Return = Unknown> {
data: T, data: T,
call_variant: ThreadsafeFunctionCallVariant, call_variant: ThreadsafeFunctionCallVariant,
callback: Box<dyn FnOnce(Result<JsUnknown>) -> Result<()>>, callback: Box<dyn FnOnce(Result<Return>, Env) -> Result<()>>,
} }
/// Communicate with the addon's main thread by invoking a JavaScript function from other threads. /// Communicate with the addon's main thread by invoking a JavaScript function from other threads.
@ -190,58 +133,70 @@ struct ThreadsafeFunctionCallJsBackData<T> {
/// An example of using `ThreadsafeFunction`: /// An example of using `ThreadsafeFunction`:
/// ///
/// ```rust /// ```rust
/// #[macro_use]
/// extern crate napi_derive;
///
/// use std::thread; /// use std::thread;
/// ///
/// use napi::{ /// use napi::{
/// threadsafe_function::{ /// threadsafe_function::{
/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, /// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,
/// }, /// },
/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,
/// }; /// };
/// use napi_derive::napi;
/// ///
/// #[js_function(1)] /// #[napi]
/// pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> { /// pub fn call_threadsafe_function(callback: ThreadsafeFunction<(u32, bool, String), ()>) {
/// let func = ctx.get::<JsFunction>(0)?;
///
/// let tsfn =
/// ctx
/// .env
/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
/// ctx.value
/// .iter()
/// .map(|v| ctx.env.create_uint32(*v))
/// .collect::<Result<Vec<JsNumber>>>()
/// })?;
///
/// let tsfn_cloned = tsfn.clone(); /// let tsfn_cloned = tsfn.clone();
/// ///
/// thread::spawn(move || { /// thread::spawn(move || {
/// let output: Vec<u32> = vec![0, 1, 2, 3]; /// let output: Vec<u32> = vec![0, 1, 2, 3];
/// // It's okay to call a threadsafe function multiple times. /// // It's okay to call a threadsafe function multiple times.
/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking); /// tsfn.call(Ok((1, false, "NAPI-RS".into())), ThreadsafeFunctionCallMode::Blocking);
/// tsfn.call(Ok((2, true, "NAPI-RS".into())), ThreadsafeFunctionCallMode::NonBlocking);
/// }); /// });
/// ///
/// thread::spawn(move || { /// thread::spawn(move || {
/// let output: Vec<u32> = vec![3, 2, 1, 0]; /// tsfn_cloned.call((3, false, "NAPI-RS".into())), ThreadsafeFunctionCallMode::NonBlocking);
/// // It's okay to call a threadsafe function multiple times.
/// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);
/// }); /// });
///
/// ctx.env.get_undefined()
/// } /// }
/// ``` /// ```
pub struct ThreadsafeFunction<T: 'static, ES: ErrorStrategy::T = ErrorStrategy::CalleeHandled> { pub struct ThreadsafeFunction<
T: 'static,
Return: FromNapiValue + 'static = Unknown,
const CalleeHandled: bool = true,
const Weak: bool = false,
const MaxQueueSize: usize = 0,
> {
handle: Arc<ThreadsafeFunctionHandle>, handle: Arc<ThreadsafeFunctionHandle>,
_phantom: PhantomData<(T, ES)>, _phantom: PhantomData<(T, Return)>,
} }
unsafe impl<T: 'static, ES: ErrorStrategy::T> Send for ThreadsafeFunction<T, ES> {} unsafe impl<
unsafe impl<T: 'static, ES: ErrorStrategy::T> Sync for ThreadsafeFunction<T, ES> {} T: 'static,
Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> Send for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
}
impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> { unsafe impl<
T: 'static,
Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> Sync for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
}
impl<
T: 'static,
Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> Clone for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
self.handle.with_read_aborted(|aborted| { self.handle.with_read_aborted(|aborted| {
if aborted { if aborted {
@ -256,86 +211,67 @@ impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> {
} }
} }
impl<T: ToNapiValue> JsValuesTupleIntoVec for T { impl<
#[allow(clippy::not_unsafe_ptr_arg_deref)] T: JsValuesTupleIntoVec + 'static,
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> { Return: FromNapiValue,
Ok(vec![unsafe { const CalleeHandled: bool,
<T as ToNapiValue>::to_napi_value(env, self)? const Weak: bool,
}]) const MaxQueueSize: usize,
} > FromNapiValue for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
}
macro_rules! impl_js_value_tuple_to_vec {
($($ident:ident),*) => {
impl<$($ident: ToNapiValue),*> JsValuesTupleIntoVec for ($($ident,)*) {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> {
#[allow(non_snake_case)]
let ($($ident,)*) = self;
Ok(vec![$(unsafe { <$ident as ToNapiValue>::to_napi_value(env, $ident)? }),*])
}
}
};
}
impl_js_value_tuple_to_vec!(A);
impl_js_value_tuple_to_vec!(A, B);
impl_js_value_tuple_to_vec!(A, B, C);
impl_js_value_tuple_to_vec!(A, B, C, D);
impl_js_value_tuple_to_vec!(A, B, C, D, E);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
impl_js_value_tuple_to_vec!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y
);
impl_js_value_tuple_to_vec!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
);
impl<T: JsValuesTupleIntoVec + 'static, ES: ErrorStrategy::T> FromNapiValue
for ThreadsafeFunction<T, ES>
{ {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
Self::create(env, napi_val, 0, |ctx| ctx.value.into_vec(ctx.env.0)) Self::create(env, napi_val, |ctx| ctx.value.into_vec(ctx.env.0))
} }
} }
impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> { impl<
/// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function) T: 'static,
/// for more information. Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
// for more information.
pub(crate) fn create< pub(crate) fn create<
V: ToNapiValue, V: ToNapiValue,
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>, R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
>( >(
env: sys::napi_env, env: sys::napi_env,
func: sys::napi_value, func: sys::napi_value,
max_queue_size: usize,
callback: R, callback: R,
) -> Result<Self> { ) -> Result<Self> {
let mut async_resource_name = ptr::null_mut(); let mut async_resource_name = ptr::null_mut();
let s = "napi_rs_threadsafe_function"; static THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME: &str = "napi_rs_threadsafe_function";
let len = s.len();
let s = CString::new(s)?; #[cfg(feature = "experimental")]
check_status!(unsafe { {
sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name) check_status!(unsafe {
})?; let mut copied = false;
sys::node_api_create_external_string_latin1(
env,
THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME.as_ptr().cast(),
27,
None,
ptr::null_mut(),
&mut async_resource_name,
&mut copied,
)
})?;
}
#[cfg(not(feature = "experimental"))]
{
check_status!(unsafe {
sys::napi_create_string_utf8(
env,
THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME.as_ptr().cast(),
27,
&mut async_resource_name,
)
})?;
}
let mut raw_tsfn = ptr::null_mut(); let mut raw_tsfn = ptr::null_mut();
let callback_ptr = Box::into_raw(Box::new(callback)); let callback_ptr = Box::into_raw(Box::new(callback));
@ -346,23 +282,35 @@ impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
func, func,
ptr::null_mut(), ptr::null_mut(),
async_resource_name, async_resource_name,
max_queue_size, MaxQueueSize,
1, 1,
Arc::downgrade(&handle).into_raw() as *mut c_void, // pass handler to thread_finalize_cb Arc::downgrade(&handle).into_raw().cast_mut().cast(), // pass handler to thread_finalize_cb
Some(thread_finalize_cb::<T, V, R>), Some(thread_finalize_cb::<T, V, R>),
callback_ptr.cast(), callback_ptr.cast(),
Some(call_js_cb::<T, V, R, ES>), Some(call_js_cb::<T, Return, V, R, CalleeHandled>),
&mut raw_tsfn, &mut raw_tsfn,
) )
})?; })?;
handle.set_raw(raw_tsfn); handle.set_raw(raw_tsfn);
// Weak ThreadsafeFunction will not prevent the event loop from exiting
if Weak {
check_status!(
unsafe { sys::napi_unref_threadsafe_function(env, raw_tsfn) },
"Unref threadsafe function failed in Weak mode"
)?;
}
Ok(ThreadsafeFunction { Ok(ThreadsafeFunction {
handle, handle,
_phantom: PhantomData, _phantom: PhantomData,
}) })
} }
#[deprecated(
since = "2.17.0",
note = "Please use `ThreadsafeFunction::clone` instead of manually increasing the reference count"
)]
/// See [napi_ref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_ref_threadsafe_function) /// See [napi_ref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_ref_threadsafe_function)
/// for more information. /// for more information.
/// ///
@ -377,6 +325,10 @@ impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
}) })
} }
#[deprecated(
since = "2.17.0",
note = "Please use `ThreadsafeFunction::clone` instead of manually decreasing the reference count"
)]
/// See [napi_unref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_unref_threadsafe_function) /// See [napi_unref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_unref_threadsafe_function)
/// for more information. /// for more information.
pub fn unref(&mut self, env: &Env) -> Result<()> { pub fn unref(&mut self, env: &Env) -> Result<()> {
@ -395,6 +347,10 @@ impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
self.handle.with_read_aborted(|aborted| aborted) self.handle.with_read_aborted(|aborted| aborted)
} }
#[deprecated(
since = "2.17.0",
note = "Drop all references to the ThreadsafeFunction will automatically release it"
)]
pub fn abort(self) -> Result<()> { pub fn abort(self) -> Result<()> {
self.handle.with_write_aborted(|mut aborted_guard| { self.handle.with_write_aborted(|mut aborted_guard| {
if !*aborted_guard { if !*aborted_guard {
@ -416,7 +372,9 @@ impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
} }
} }
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> { impl<T: 'static, Return: FromNapiValue + 'static, const Weak: bool, const MaxQueueSize: usize>
ThreadsafeFunction<T, Return, true, { Weak }, { MaxQueueSize }>
{
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
/// for more information. /// for more information.
pub fn call(&self, value: Result<T>, mode: ThreadsafeFunctionCallMode) -> Status { pub fn call(&self, value: Result<T>, mode: ThreadsafeFunctionCallMode) -> Status {
@ -432,7 +390,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
ThreadsafeFunctionCallJsBackData { ThreadsafeFunctionCallJsBackData {
data, data,
call_variant: ThreadsafeFunctionCallVariant::Direct, call_variant: ThreadsafeFunctionCallVariant::Direct,
callback: Box::new(|_d: Result<JsUnknown>| Ok(())), callback: Box::new(|_d: Result<Return>, _| Ok(())),
} }
}))) })))
.cast(), .cast(),
@ -443,7 +401,8 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
}) })
} }
pub fn call_with_return_value<D: FromNapiValue, F: 'static + FnOnce(D) -> Result<()>>( /// Call the ThreadsafeFunction, and handle the return value with a callback
pub fn call_with_return_value<F: 'static + FnOnce(Result<Return>, Env) -> Result<()>>(
&self, &self,
value: Result<T>, value: Result<T>,
mode: ThreadsafeFunctionCallMode, mode: ThreadsafeFunctionCallMode,
@ -461,9 +420,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
ThreadsafeFunctionCallJsBackData { ThreadsafeFunctionCallJsBackData {
data, data,
call_variant: ThreadsafeFunctionCallVariant::WithCallback, call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| { callback: Box::new(move |d: Result<Return>, env: Env| cb(d, env)),
d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb))
}),
} }
}))) })))
.cast(), .cast(),
@ -475,8 +432,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
} }
#[cfg(feature = "tokio_rt")] #[cfg(feature = "tokio_rt")]
pub async fn call_async<D: 'static + FromNapiValue>(&self, value: Result<T>) -> Result<D> { /// Call the ThreadsafeFunction, and handle the return value with in `async` way
let (sender, receiver) = tokio::sync::oneshot::channel::<Result<D>>(); pub async fn call_async(&self, value: Result<T>) -> Result<Return> {
let (sender, receiver) = tokio::sync::oneshot::channel::<Result<Return>>();
self.handle.with_read_aborted(|aborted| { self.handle.with_read_aborted(|aborted| {
if aborted { if aborted {
@ -491,9 +449,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
ThreadsafeFunctionCallJsBackData { ThreadsafeFunctionCallJsBackData {
data, data,
call_variant: ThreadsafeFunctionCallVariant::WithCallback, call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| { callback: Box::new(move |d: Result<Return>, _| {
sender sender
.send(d.and_then(|d| D::from_napi_value(d.0.env, d.0.value))) .send(d)
// The only reason for send to return Err is if the receiver isn't listening // The only reason for send to return Err is if the receiver isn't listening
// Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead. // Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead.
.or(Ok(())) .or(Ok(()))
@ -519,7 +477,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
} }
} }
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> { impl<T: 'static, Return: FromNapiValue + 'static, const Weak: bool, const MaxQueueSize: usize>
ThreadsafeFunction<T, Return, false, { Weak }, { MaxQueueSize }>
{
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
/// for more information. /// for more information.
pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status { pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status {
@ -534,7 +494,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
data: value, data: value,
call_variant: ThreadsafeFunctionCallVariant::Direct, call_variant: ThreadsafeFunctionCallVariant::Direct,
callback: Box::new(|_d: Result<JsUnknown>| Ok(())), callback: Box::new(|_d: Result<Return>, _: Env| Ok(())),
})) }))
.cast(), .cast(),
mode.into(), mode.into(),
@ -544,7 +504,8 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
}) })
} }
pub fn call_with_return_value<D: FromNapiValue, F: 'static + FnOnce(D) -> Result<()>>( /// Call the ThreadsafeFunction, and handle the return value with a callback
pub fn call_with_return_value<F: 'static + FnOnce(Result<Return>, Env) -> Result<()>>(
&self, &self,
value: T, value: T,
mode: ThreadsafeFunctionCallMode, mode: ThreadsafeFunctionCallMode,
@ -561,9 +522,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
data: value, data: value,
call_variant: ThreadsafeFunctionCallVariant::WithCallback, call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| { callback: Box::new(cb),
d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb))
}),
})) }))
.cast(), .cast(),
mode.into(), mode.into(),
@ -574,8 +533,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
} }
#[cfg(feature = "tokio_rt")] #[cfg(feature = "tokio_rt")]
pub async fn call_async<D: 'static + FromNapiValue>(&self, value: T) -> Result<D> { /// Call the ThreadsafeFunction, and handle the return value with in `async` way
let (sender, receiver) = tokio::sync::oneshot::channel::<D>(); pub async fn call_async(&self, value: T) -> Result<Return> {
let (sender, receiver) = tokio::sync::oneshot::channel::<Return>();
self.handle.with_read_aborted(|aborted| { self.handle.with_read_aborted(|aborted| {
if aborted { if aborted {
@ -588,15 +548,13 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
data: value, data: value,
call_variant: ThreadsafeFunctionCallVariant::WithCallback, call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| { callback: Box::new(move |d, _| {
d.and_then(|d| { d.and_then(|d| {
D::from_napi_value(d.0.env, d.0.value).and_then(move |d| { sender
sender .send(d)
.send(d) // The only reason for send to return Err is if the receiver isn't listening
// The only reason for send to return Err is if the receiver isn't listening // Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead.
// Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead. .or(Ok(()))
.or(Ok(()))
})
}) })
}), }),
})) }))
@ -612,16 +570,15 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
} }
} }
#[allow(unused_variables)]
unsafe extern "C" fn thread_finalize_cb<T: 'static, V: ToNapiValue, R>( unsafe extern "C" fn thread_finalize_cb<T: 'static, V: ToNapiValue, R>(
env: sys::napi_env, #[allow(unused_variables)] env: sys::napi_env,
finalize_data: *mut c_void, finalize_data: *mut c_void,
finalize_hint: *mut c_void, finalize_hint: *mut c_void,
) where ) where
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>, R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
{ {
let handle_option = let handle_option: Option<Arc<ThreadsafeFunctionHandle>> =
unsafe { Weak::from_raw(finalize_data.cast::<ThreadsafeFunctionHandle>()).upgrade() }; unsafe { sync::Weak::from_raw(finalize_data.cast()).upgrade() };
if let Some(handle) = handle_option { if let Some(handle) = handle_option {
handle.with_write_aborted(|mut aborted_guard| { handle.with_write_aborted(|mut aborted_guard| {
@ -635,29 +592,31 @@ unsafe extern "C" fn thread_finalize_cb<T: 'static, V: ToNapiValue, R>(
drop(unsafe { Box::<R>::from_raw(finalize_hint.cast()) }); drop(unsafe { Box::<R>::from_raw(finalize_hint.cast()) });
} }
unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>( unsafe extern "C" fn call_js_cb<
T: 'static,
Return: FromNapiValue,
V: ToNapiValue,
R,
const CalleeHandled: bool,
>(
raw_env: sys::napi_env, raw_env: sys::napi_env,
js_callback: sys::napi_value, js_callback: sys::napi_value,
context: *mut c_void, context: *mut c_void,
data: *mut c_void, data: *mut c_void,
) where ) where
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>, R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
ES: ErrorStrategy::T,
{ {
// env and/or callback can be null when shutting down // env and/or callback can be null when shutting down
if raw_env.is_null() || js_callback.is_null() { if raw_env.is_null() || js_callback.is_null() {
return; return;
} }
let ctx: &mut R = unsafe { Box::leak(Box::from_raw(context.cast())) }; let callback: &mut R = unsafe { Box::leak(Box::from_raw(context.cast())) };
let val = unsafe { let val = unsafe {
match ES::VALUE { if CalleeHandled {
ErrorStrategy::CalleeHandled::VALUE => { *Box::<Result<ThreadsafeFunctionCallJsBackData<T, Return>>>::from_raw(data.cast())
*Box::<Result<ThreadsafeFunctionCallJsBackData<T>>>::from_raw(data.cast()) } else {
} Ok(*Box::<ThreadsafeFunctionCallJsBackData<T, Return>>::from_raw(data.cast()))
ErrorStrategy::Fatal::VALUE => Ok(*Box::<ThreadsafeFunctionCallJsBackData<T>>::from_raw(
data.cast(),
)),
} }
}; };
@ -665,8 +624,8 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
unsafe { sys::napi_get_undefined(raw_env, &mut recv) }; unsafe { sys::napi_get_undefined(raw_env, &mut recv) };
let ret = val.and_then(|v| { let ret = val.and_then(|v| {
(ctx)(ThreadSafeCallContext { (callback)(ThreadsafeCallContext {
env: unsafe { Env::from_raw(raw_env) }, env: Env::from_raw(raw_env),
value: v.data, value: v.data,
}) })
.map(|ret| (ret, v.call_variant, v.callback)) .map(|ret| (ret, v.call_variant, v.callback))
@ -680,7 +639,7 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
let values = values let values = values
.into_iter() .into_iter()
.map(|v| unsafe { ToNapiValue::to_napi_value(raw_env, v) }); .map(|v| unsafe { ToNapiValue::to_napi_value(raw_env, v) });
let args: Result<Vec<sys::napi_value>> = if ES::VALUE == ErrorStrategy::CalleeHandled::VALUE { let args: Result<Vec<sys::napi_value>> = if CalleeHandled {
let mut js_null = ptr::null_mut(); let mut js_null = ptr::null_mut();
unsafe { sys::napi_get_null(raw_env, &mut js_null) }; unsafe { sys::napi_get_null(raw_env, &mut js_null) };
::core::iter::once(Ok(js_null)).chain(values).collect() ::core::iter::once(Ok(js_null)).chain(values).collect()
@ -699,62 +658,45 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
&mut return_value, &mut return_value,
) )
}, },
Err(e) => match ES::VALUE { Err(e) => {
ErrorStrategy::Fatal::VALUE => unsafe { if CalleeHandled {
sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) unsafe { sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) }
}, } else {
ErrorStrategy::CalleeHandled::VALUE => unsafe { unsafe {
sys::napi_call_function( sys::napi_call_function(
raw_env, raw_env,
recv, recv,
js_callback, js_callback,
1, 1,
[JsError::from(e).into_value(raw_env)].as_mut_ptr(), [JsError::from(e).into_value(raw_env)].as_mut_ptr(),
&mut return_value, &mut return_value,
) )
}, }
}, }
}
}; };
if let ThreadsafeFunctionCallVariant::WithCallback = call_variant { if let ThreadsafeFunctionCallVariant::WithCallback = call_variant {
// throw Error in JavaScript callback // throw Error in JavaScript callback
let callback_arg = if status == sys::Status::napi_pending_exception { let callback_arg = if status == sys::Status::napi_pending_exception {
let mut exception = ptr::null_mut(); let mut exception = ptr::null_mut();
status = unsafe { sys::napi_get_and_clear_last_exception(raw_env, &mut exception) }; status = unsafe { sys::napi_get_and_clear_last_exception(raw_env, &mut exception) };
Err( let mut error_reference = ptr::null_mut();
JsUnknown(crate::Value { unsafe { sys::napi_create_reference(raw_env, exception, 1, &mut error_reference) };
env: raw_env, Err(Error {
value: exception, maybe_raw: error_reference,
value_type: crate::ValueType::Unknown, status: Status::from(status),
}) reason: "".to_owned(),
.into(), })
)
} else { } else {
Ok(JsUnknown(crate::Value { unsafe { Return::from_napi_value(raw_env, return_value) }
env: raw_env,
value: return_value,
value_type: crate::ValueType::Unknown,
}))
}; };
if let Err(err) = callback(callback_arg) { if let Err(err) = callback(callback_arg, Env::from_raw(raw_env)) {
let message = format!( unsafe { sys::napi_fatal_exception(raw_env, JsError::from(err).into_value(raw_env)) };
"Failed to convert return value in ThreadsafeFunction callback into Rust value: {}",
err
);
let message_length = message.len();
let c_message = CString::new(message).unwrap();
unsafe {
sys::napi_fatal_error(
"threadsafe_function.rs:749\0".as_ptr().cast(),
26,
c_message.as_ptr(),
message_length,
)
};
} }
} }
status status
} }
Err(e) if ES::VALUE == ErrorStrategy::Fatal::VALUE => unsafe { Err(e) if !CalleeHandled => unsafe {
sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env))
}, },
Err(e) => unsafe { Err(e) => unsafe {
@ -783,27 +725,27 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception); assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception);
} else { } else {
let error_code: Status = status.into(); let error_code: Status = status.into();
let error_code_string = format!("{:?}", error_code); let error_code_string = format!("{}", error_code);
let mut error_code_value = ptr::null_mut(); let mut error_code_value = ptr::null_mut();
assert_eq!( assert_eq!(
unsafe { unsafe {
sys::napi_create_string_utf8( sys::napi_create_string_utf8(
raw_env, raw_env,
error_code_string.as_ptr() as *const _, error_code_string.as_ptr().cast(),
error_code_string.len(), error_code_string.len(),
&mut error_code_value, &mut error_code_value,
) )
}, },
sys::Status::napi_ok, sys::Status::napi_ok,
); );
let error_msg = "Call JavaScript callback failed in threadsafe function"; static ERROR_MSG: &str = "Call JavaScript callback failed in threadsafe function";
let mut error_msg_value = ptr::null_mut(); let mut error_msg_value = ptr::null_mut();
assert_eq!( assert_eq!(
unsafe { unsafe {
sys::napi_create_string_utf8( sys::napi_create_string_utf8(
raw_env, raw_env,
error_msg.as_ptr() as *const _, ERROR_MSG.as_ptr().cast(),
error_msg.len(), ERROR_MSG.len(),
&mut error_msg_value, &mut error_msg_value,
) )
}, },
@ -823,99 +765,6 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
} }
} }
/// Helper
macro_rules! type_level_enum {(
$( #[doc = $doc:tt] )*
$pub:vis
enum $EnumName:ident {
$(
$( #[doc = $doc_variant:tt] )*
$Variant:ident
),* $(,)?
}
) => (type_level_enum! { // This requires the macro to be in scope when called.
with_docs! {
$( #[doc = $doc] )*
///
/// ### Type-level `enum`
///
/// Until `const_generics` can handle custom `enum`s, this pattern must be
/// implemented at the type level.
///
/// We thus end up with:
///
/// ```rust,ignore
/// #[type_level_enum]
#[doc = ::core::concat!(
" enum ", ::core::stringify!($EnumName), " {",
)]
$(
#[doc = ::core::concat!(
" ", ::core::stringify!($Variant), ",",
)]
)*
#[doc = " }"]
/// ```
///
#[doc = ::core::concat!(
"With [`", ::core::stringify!($EnumName), "::T`](#reexports) \
being the type-level \"enum type\":",
)]
///
/// ```rust,ignore
#[doc = ::core::concat!(
"<Param: ", ::core::stringify!($EnumName), "::T>"
)]
/// ```
}
#[allow(warnings)]
$pub mod $EnumName {
#[doc(no_inline)]
pub use $EnumName as T;
super::type_level_enum! {
with_docs! {
#[doc = ::core::concat!(
"See [`", ::core::stringify!($EnumName), "`]\
[super::", ::core::stringify!($EnumName), "]"
)]
}
pub trait $EnumName : __sealed::$EnumName + ::core::marker::Sized + 'static {
const VALUE: __value::$EnumName;
}
}
mod __sealed { pub trait $EnumName {} }
mod __value {
#[derive(Debug, PartialEq, Eq)]
pub enum $EnumName { $( $Variant ),* }
}
$(
$( #[doc = $doc_variant] )*
pub enum $Variant {}
impl __sealed::$EnumName for $Variant {}
impl $EnumName for $Variant {
const VALUE: __value::$EnumName = __value::$EnumName::$Variant;
}
impl $Variant {
pub const VALUE: __value::$EnumName = __value::$EnumName::$Variant;
}
)*
}
});(
with_docs! {
$( #[doc = $doc:expr] )*
}
$item:item
) => (
$( #[doc = $doc] )*
$item
)}
use type_level_enum;
pub struct UnknownReturnValue; pub struct UnknownReturnValue;
impl TypeName for UnknownReturnValue { impl TypeName for UnknownReturnValue {

View file

@ -36,7 +36,7 @@ test('should be able to create function from closure', (t) => {
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
t.is( t.is(
bindings.testCreateFunctionFromClosure()( bindings.testCreateFunctionFromClosure()(
...Array.from({ length: i }).map((_, i) => i), ...Array.from({ length: i }, (_, i) => i),
), ),
`arguments length: ${i}`, `arguments length: ${i}`,
) )

View file

@ -1,13 +1,16 @@
use std::convert::TryInto; use std::convert::TryInto;
use napi::{CallContext, JsFunction, JsNumber, JsObject, JsUndefined, Property, Result}; use napi::{
bindgen_prelude::{Function, Unknown},
CallContext, JsNumber, JsObject, JsUndefined, Property, Result,
};
struct NativeClass { struct NativeClass {
value: i32, value: i32,
} }
#[js_function(1)] #[js_function(1)]
fn create_test_class(ctx: CallContext) -> Result<JsFunction> { fn create_test_class(ctx: CallContext) -> Result<Function<Unknown, Unknown>> {
let add_count_method = Property::new("addCount")?.with_method(add_count); let add_count_method = Property::new("addCount")?.with_method(add_count);
let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count);
let renew_wrapped = Property::new("renewWrapped")?.with_method(renew_wrapped); let renew_wrapped = Property::new("renewWrapped")?.with_method(renew_wrapped);
@ -56,7 +59,7 @@ fn renew_wrapped(ctx: CallContext) -> Result<JsUndefined> {
} }
#[js_function(1)] #[js_function(1)]
fn new_test_class(ctx: CallContext) -> Result<JsObject> { fn new_test_class(ctx: CallContext) -> Result<Unknown> {
let add_count_method = Property::new("addCount")?.with_method(add_count); let add_count_method = Property::new("addCount")?.with_method(add_count);
let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count);
let properties = vec![add_count_method, add_native_count]; let properties = vec![add_count_method, add_native_count];
@ -65,7 +68,7 @@ fn new_test_class(ctx: CallContext) -> Result<JsObject> {
.env .env
.define_class("TestClass", test_class_constructor, properties.as_slice())?; .define_class("TestClass", test_class_constructor, properties.as_slice())?;
test_class.new_instance(&[ctx.env.create_int32(42)?]) test_class.new_instance(42)
} }
pub fn register_js(exports: &mut JsObject) -> Result<()> { pub fn register_js(exports: &mut JsObject) -> Result<()> {

View file

@ -1,4 +1,6 @@
use napi::{CallContext, JsError, JsFunction, JsNull, JsObject, JsUnknown, Result}; use napi::{
bindgen_prelude::Function, CallContext, JsError, JsFunction, JsNull, JsObject, JsUnknown, Result,
};
#[js_function(1)] #[js_function(1)]
pub fn call_function(ctx: CallContext) -> Result<JsNull> { pub fn call_function(ctx: CallContext) -> Result<JsNull> {
@ -44,15 +46,16 @@ pub fn call_function_error(ctx: CallContext) -> Result<JsUnknown> {
} }
#[js_function(0)] #[js_function(0)]
pub fn test_create_function_from_closure(ctx: CallContext) -> Result<JsFunction> { pub fn test_create_function_from_closure(ctx: CallContext) -> Result<Function<u32, String>> {
ctx ctx
.env .env
.create_function_from_closure("functionFromClosure", move |ctx| { .create_function_from_closure("functionFromClosure", move |ctx| {
if ctx.length != 0 { if ctx.length() != 0 {
let max: u32 = ctx.get(ctx.length - 1)?; let args = ctx.arguments::<u32>()?;
assert_eq!(max, ctx.length as u32 - 1); let max = args.last().unwrap();
assert_eq!(*max, ctx.length() as u32 - 1);
} }
Ok(format!("arguments length: {}", ctx.length)) Ok(format!("arguments length: {}", ctx.length()))
}) })
} }

View file

@ -1,10 +1,10 @@
use std::convert::TryInto; use std::convert::TryInto;
use napi::{CallContext, JsFunction, JsNumber, JsObject, JsTimeout, JsUndefined, Result}; use napi::{CallContext, JsNumber, JsObject, JsTimeout, JsUndefined, Result};
#[js_function(2)] #[js_function(2)]
pub fn set_timeout(ctx: CallContext) -> Result<JsTimeout> { pub fn set_timeout(ctx: CallContext) -> Result<JsTimeout> {
let handler: JsFunction = ctx.get(0)?; let handler = ctx.get(0)?;
let timeout: JsNumber = ctx.get(1)?; let timeout: JsNumber = ctx.get(1)?;
ctx ctx
.env .env

View file

@ -1,5 +1,6 @@
#![allow(unused_variables)] #![allow(unused_variables)]
#![allow(clippy::uninlined_format_args)] #![allow(clippy::uninlined_format_args)]
#![allow(deprecated)]
#[macro_use] #[macro_use]
extern crate napi_derive; extern crate napi_derive;

View file

@ -1,4 +1,4 @@
use napi::{Env, JsObject, Property, Result}; use napi::{bindgen_prelude::Unknown, Env, JsObject, Property, Result};
mod deferred; mod deferred;
mod tsfn; mod tsfn;
@ -26,7 +26,7 @@ pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> {
exports.create_named_method("testTsfnWithRef", test_tsfn_with_ref)?; exports.create_named_method("testTsfnWithRef", test_tsfn_with_ref)?;
exports.create_named_method("testDeferred", deferred::test_deferred)?; exports.create_named_method("testDeferred", deferred::test_deferred)?;
let obj = env.define_class( let obj = env.define_class::<Unknown>(
"A", "A",
constructor, constructor,
&[ &[

View file

@ -251,7 +251,7 @@ Generated by [AVA](https://avajs.dev).
export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void␊ export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void␊
export function acceptThreadsafeFunctionFatal(func: (arg: number) => any): void␊ export function acceptThreadsafeFunctionFatal(func: (arg: number) => void): void␊
export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void␊ export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void␊
@ -310,6 +310,8 @@ Generated by [AVA](https://avajs.dev).
export function bufferPassThrough(buf: Buffer): Promise<Buffer> export function bufferPassThrough(buf: Buffer): Promise<Buffer>
export function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void␊
export interface C {␊ export interface C {␊
baz: number␊ baz: number␊
}␊ }␊
@ -330,9 +332,9 @@ Generated by [AVA](https://avajs.dev).
export function callFunctionWithArgAndCtx(ctx: Animal, cb: (arg: string) => void, name: string): void␊ export function callFunctionWithArgAndCtx(ctx: Animal, cb: (arg: string) => void, name: string): void␊
export function callLongThreadsafeFunction(callback: (...args: any[]) => any): void␊ export function callLongThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void␊
export function callThreadsafeFunction(callback: (...args: any[]) => any): void␊ export function callThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void␊
export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊ export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊
@ -639,11 +641,11 @@ Generated by [AVA](https://avajs.dev).
export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void␊ export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void␊
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void␊ export function threadsafeFunctionFatalMode(cb: (arg: boolean) => unknown): void␊
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void␊ export function threadsafeFunctionFatalModeError(cb: (arg: boolean) => string): void␊
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊ export function threadsafeFunctionThrowError(cb: (err: Error | null, arg: boolean) => unknown): void␊
export function throwAsyncError(): Promise<void> export function throwAsyncError(): Promise<void>
@ -653,15 +655,15 @@ Generated by [AVA](https://avajs.dev).
export function toJsObj(): object␊ export function toJsObj(): object␊
export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void> export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise<void>
export function tsfnCallWithCallback(func: (...args: any[]) => any): void␊ export function tsfnCallWithCallback(tsfn: (err: Error | null, ) => string): void␊
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => any): Promise<number> export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise<number> export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise<number> export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsRename(a: { foo: number }): string[]␊ export function tsRename(a: { foo: number }): string[]␊

View file

@ -19,6 +19,7 @@ Generated by [AVA](https://avajs.dev).
'cross-env', 'cross-env',
'electron', 'electron',
'lodash', 'lodash',
'rxjs',
'sinon', 'sinon',
'vite', 'vite',
'vite-plugin-node-polyfills', 'vite-plugin-node-polyfills',

View file

@ -1,5 +1,5 @@
import('../index.cjs').then( const { threadsafeFunctionFatalModeError } = require('../index.cjs')
({ threadsafeFunctionFatalModeError }) => {
return threadsafeFunctionFatalModeError(() => {}) threadsafeFunctionFatalModeError(() => {
}, return false
) })

View file

@ -2,6 +2,7 @@ import { exec } from 'node:child_process'
import { join } from 'node:path' import { join } from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { Subject, take } from 'rxjs'
import { spy } from 'sinon' import { spy } from 'sinon'
import { import {
@ -170,6 +171,7 @@ import {
throwSyntaxError, throwSyntaxError,
type AliasedStruct, type AliasedStruct,
returnObjectOnlyToJs, returnObjectOnlyToJs,
buildThreadsafeFunctionFromFunction,
} from '../index.cjs' } from '../index.cjs'
import { test } from './test.framework.js' import { test } from './test.framework.js'
@ -968,7 +970,7 @@ BigIntTest('from i128 i64', (t) => {
t.is(bigintFromI128(), BigInt('-100')) t.is(bigintFromI128(), BigInt('-100'))
}) })
Napi4Test('call thread safe function', (t) => { Napi4Test('call ThreadsafeFunction', (t) => {
let i = 0 let i = 0
let value = 0 let value = 0
return new Promise((resolve) => { return new Promise((resolve) => {
@ -980,14 +982,14 @@ Napi4Test('call thread safe function', (t) => {
resolve() resolve()
t.is( t.is(
value, value,
Array.from({ length: 100 }, (_, i) => i + 1).reduce((a, b) => a + b), Array.from({ length: 100 }, (_, i) => i).reduce((a, b) => a + b),
) )
} }
}) })
}) })
}) })
Napi4Test('throw error from thread safe function', async (t) => { Napi4Test('throw error from ThreadsafeFunction', async (t) => {
const throwPromise = new Promise((_, reject) => { const throwPromise = new Promise((_, reject) => {
threadsafeFunctionThrowError(reject) threadsafeFunctionThrowError(reject)
}) })
@ -995,7 +997,7 @@ Napi4Test('throw error from thread safe function', async (t) => {
t.is(err?.message, 'ThrowFromNative') t.is(err?.message, 'ThrowFromNative')
}) })
Napi4Test('thread safe function closure capture data', (t) => { Napi4Test('ThreadsafeFunction closure capture data', (t) => {
return new Promise((resolve) => { return new Promise((resolve) => {
threadsafeFunctionClosureCapture(() => { threadsafeFunctionClosureCapture(() => {
resolve() resolve()
@ -1024,7 +1026,7 @@ Napi4Test('throw error from thread safe function fatal mode', (t) => {
t.is(code, 1) t.is(code, 1)
const stderrMsg = stderr.toString('utf8') const stderrMsg = stderr.toString('utf8')
console.info(stderrMsg) console.info(stderrMsg)
t.true(stderrMsg.includes(`Error: Generic tsfn error`)) t.true(stderrMsg.includes(`Error: Failed to convert JavaScript value`))
resolve() resolve()
}) })
}) })
@ -1060,8 +1062,7 @@ Napi4Test('call ThreadsafeFunction with callback', async (t) => {
Napi4Test('async call ThreadsafeFunction', async (t) => { Napi4Test('async call ThreadsafeFunction', async (t) => {
await t.notThrowsAsync(() => await t.notThrowsAsync(() =>
tsfnAsyncCall((err, arg1, arg2, arg3) => { tsfnAsyncCall((arg1, arg2, arg3) => {
t.is(err, null)
t.is(arg1, 0) t.is(arg1, 0)
t.is(arg2, 1) t.is(arg2, 1)
t.is(arg3, 2) t.is(arg3, 2)
@ -1163,6 +1164,20 @@ Napi4Test('object only from js', (t) => {
}) })
}) })
Napi4Test('build ThreadsafeFunction from Function', (t) => {
const subject = new Subject<void>()
const fn = (a: number, b: number) => {
t.is(a, 1)
t.is(b, 2)
subject.next()
return a * b
}
buildThreadsafeFunctionFromFunction(fn)
return subject.pipe(take(3))
})
Napi4Test('promise in either', async (t) => { Napi4Test('promise in either', async (t) => {
t.is(await promiseInEither(1), false) t.is(await promiseInEither(1), false)
t.is(await promiseInEither(20), true) t.is(await promiseInEither(20), true)

View file

@ -85,7 +85,7 @@ async function main() {
assert( assert(
value === value ===
Array.from({ length: 100 }, (_, i) => i + 1).reduce((a, b) => a + b), Array.from({ length: 100 }, (_, i) => i).reduce((a, b) => a + b),
) )
console.info(createExternalTypedArray()) console.info(createExternalTypedArray())
} }

View file

@ -398,6 +398,7 @@ module.exports.bigintFromI128 = nativeBinding.bigintFromI128
module.exports.bigintFromI64 = nativeBinding.bigintFromI64 module.exports.bigintFromI64 = nativeBinding.bigintFromI64
module.exports.bigintGetU64AsString = nativeBinding.bigintGetU64AsString module.exports.bigintGetU64AsString = nativeBinding.bigintGetU64AsString
module.exports.bufferPassThrough = nativeBinding.bufferPassThrough module.exports.bufferPassThrough = nativeBinding.bufferPassThrough
module.exports.buildThreadsafeFunctionFromFunction = nativeBinding.buildThreadsafeFunctionFromFunction
module.exports.call0 = nativeBinding.call0 module.exports.call0 = nativeBinding.call0
module.exports.call1 = nativeBinding.call1 module.exports.call1 = nativeBinding.call1
module.exports.call2 = nativeBinding.call2 module.exports.call2 = nativeBinding.call2

View file

@ -241,7 +241,7 @@ export function acceptSlice(fixture: Uint8Array): bigint
export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void
export function acceptThreadsafeFunctionFatal(func: (arg: number) => any): void export function acceptThreadsafeFunctionFatal(func: (arg: number) => void): void
export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void
@ -300,6 +300,8 @@ export function bigintGetU64AsString(bi: bigint): string
export function bufferPassThrough(buf: Buffer): Promise<Buffer> export function bufferPassThrough(buf: Buffer): Promise<Buffer>
export function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void
export interface C { export interface C {
baz: number baz: number
} }
@ -320,9 +322,9 @@ export function callFunctionWithArg(cb: (arg0: number, arg1: number) => number,
export function callFunctionWithArgAndCtx(ctx: Animal, cb: (arg: string) => void, name: string): void export function callFunctionWithArgAndCtx(ctx: Animal, cb: (arg: string) => void, name: string): void
export function callLongThreadsafeFunction(callback: (...args: any[]) => any): void export function callLongThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void
export function callThreadsafeFunction(callback: (...args: any[]) => any): void export function callThreadsafeFunction(tsfn: (err: Error | null, arg: number) => unknown): void
export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void
@ -629,11 +631,11 @@ export function testSerdeRoundtrip(data: any): any
export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void export function threadsafeFunctionClosureCapture(func: (...args: any[]) => any): void
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void export function threadsafeFunctionFatalMode(cb: (arg: boolean) => unknown): void
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void export function threadsafeFunctionFatalModeError(cb: (arg: boolean) => string): void
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void export function threadsafeFunctionThrowError(cb: (err: Error | null, arg: boolean) => unknown): void
export function throwAsyncError(): Promise<void> export function throwAsyncError(): Promise<void>
@ -643,15 +645,15 @@ export function throwSyntaxError(error: string, code?: string | undefined | null
export function toJsObj(): object export function toJsObj(): object
export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void> export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise<void>
export function tsfnCallWithCallback(func: (...args: any[]) => any): void export function tsfnCallWithCallback(tsfn: (err: Error | null, ) => string): void
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => any): Promise<number> export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise<number> export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise<number> export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsRename(a: { foo: number }): string[] export function tsRename(a: { foo: number }): string[]

View file

@ -24,6 +24,7 @@
"cross-env": "7.0.3", "cross-env": "7.0.3",
"electron": "^29.0.1", "electron": "^29.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"rxjs": "^7.8.1",
"sinon": "^17.0.1", "sinon": "^17.0.1",
"vite": "^5.0.12", "vite": "^5.0.12",
"vite-plugin-node-polyfills": "^0.19.0", "vite-plugin-node-polyfills": "^0.19.0",

View file

@ -2,7 +2,7 @@ use std::{env, format};
use napi::{ use napi::{
bindgen_prelude::*, bindgen_prelude::*,
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode},
JsUnknown, JsUnknown,
}; };
@ -65,7 +65,7 @@ fn callback_return_promise<T: Fn() -> Result<JsUnknown>>(
if ret.is_promise()? { if ret.is_promise()? {
let p = Promise::<String>::from_unknown(ret)?; let p = Promise::<String>::from_unknown(ret)?;
let fn_out_tsfn: ThreadsafeFunction<String> = fn_out let fn_out_tsfn: ThreadsafeFunction<String> = fn_out
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<String>| Ok(vec![ctx.value]))?; .create_threadsafe_function(|ctx: ThreadsafeCallContext<String>| Ok(vec![ctx.value]))?;
env env
.execute_tokio_future( .execute_tokio_future(
async move { async move {

View file

@ -15,7 +15,7 @@ fn chrono_date_to_millis(input: chrono::DateTime<Utc>) -> i64 {
#[napi] #[napi]
fn chrono_date_add_1_minute(input: chrono::DateTime<Utc>) -> chrono::DateTime<Utc> { fn chrono_date_add_1_minute(input: chrono::DateTime<Utc>) -> chrono::DateTime<Utc> {
input + Duration::minutes(1) Duration::try_minutes(1).map(|d| input + d).unwrap()
} }
#[napi(object)] #[napi(object)]
@ -26,7 +26,7 @@ pub struct Dates {
#[napi] #[napi]
pub fn chrono_native_date_time(date: chrono::NaiveDateTime) -> i64 { pub fn chrono_native_date_time(date: chrono::NaiveDateTime) -> i64 {
date.timestamp_millis() date.and_utc().timestamp_millis()
} }
#[napi] #[napi]

View file

@ -19,6 +19,7 @@ pub enum Status {
Ready, Ready,
} }
#[allow(clippy::enum_variant_names)]
#[napi(string_enum = "lowercase")] #[napi(string_enum = "lowercase")]
pub enum StringEnum { pub enum StringEnum {
VariantOne, VariantOne,

View file

@ -1,5 +1,8 @@
#![allow(deprecated)]
use napi::{ use napi::{
bindgen_prelude::{ClassInstance, Function, FunctionRef}, bindgen_prelude::{ClassInstance, Function, FunctionRef},
threadsafe_function::ThreadsafeFunctionCallMode,
Env, JsFunction, JsObject, Result, Env, JsFunction, JsObject, Result,
}; };
@ -74,3 +77,30 @@ pub fn reference_as_callback(
) -> Result<u32> { ) -> Result<u32> {
callback.borrow_back(&env)?.call((arg0, arg1)) callback.borrow_back(&env)?.call((arg0, arg1))
} }
#[napi]
pub fn build_threadsafe_function_from_function(callback: Function<(u32, u32), u32>) -> Result<()> {
let tsfn = callback.build_threadsafe_function().build()?;
std::thread::spawn(move || {
tsfn.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking);
});
let tsfn_max_queue_size_1 = callback
.build_threadsafe_function()
.max_queue_size::<1>()
.build()?;
std::thread::spawn(move || {
tsfn_max_queue_size_1.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking);
});
let tsfn_weak = callback
.build_threadsafe_function()
.weak::<true>()
.build()?;
std::thread::spawn(move || {
tsfn_weak.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking);
});
Ok(())
}

View file

@ -3,6 +3,7 @@
#![allow(clippy::disallowed_names)] #![allow(clippy::disallowed_names)]
#![allow(clippy::uninlined_format_args)] #![allow(clippy::uninlined_format_args)]
#![allow(clippy::new_without_default)] #![allow(clippy::new_without_default)]
#![allow(deprecated)]
#[macro_use] #[macro_use]
extern crate napi_derive; extern crate napi_derive;

View file

@ -2,14 +2,12 @@ use std::{thread, time::Duration};
use napi::{ use napi::{
bindgen_prelude::*, bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode, UnknownReturnValue},
JsBoolean, JsString, JsString,
}; };
#[napi] #[napi]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> { pub fn call_threadsafe_function(tsfn: ThreadsafeFunction<u32, UnknownReturnValue>) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> =
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?;
for n in 0..100 { for n in 0..100 {
let tsfn = tsfn.clone(); let tsfn = tsfn.clone();
thread::spawn(move || { thread::spawn(move || {
@ -20,9 +18,9 @@ pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
} }
#[napi] #[napi]
pub fn call_long_threadsafe_function(callback: JsFunction) -> Result<()> { pub fn call_long_threadsafe_function(
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = tsfn: ThreadsafeFunction<u32, UnknownReturnValue>,
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?; ) -> Result<()> {
thread::spawn(move || { thread::spawn(move || {
for n in 0..10 { for n in 0..10 {
thread::sleep(Duration::from_millis(100)); thread::sleep(Duration::from_millis(100));
@ -33,11 +31,11 @@ pub fn call_long_threadsafe_function(callback: JsFunction) -> Result<()> {
} }
#[napi] #[napi]
pub fn threadsafe_function_throw_error(cb: JsFunction) -> Result<()> { pub fn threadsafe_function_throw_error(
let tsfn: ThreadsafeFunction<bool, ErrorStrategy::CalleeHandled> = cb: ThreadsafeFunction<bool, UnknownReturnValue>,
cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?; ) -> Result<()> {
thread::spawn(move || { thread::spawn(move || {
tsfn.call( cb.call(
Err(Error::new( Err(Error::new(
Status::GenericFailure, Status::GenericFailure,
"ThrowFromNative".to_owned(), "ThrowFromNative".to_owned(),
@ -49,26 +47,23 @@ pub fn threadsafe_function_throw_error(cb: JsFunction) -> Result<()> {
} }
#[napi] #[napi]
pub fn threadsafe_function_fatal_mode(cb: JsFunction) -> Result<()> { pub fn threadsafe_function_fatal_mode(
let tsfn: ThreadsafeFunction<bool, ErrorStrategy::Fatal> = cb: ThreadsafeFunction<bool, UnknownReturnValue, false>,
cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?; ) -> Result<()> {
thread::spawn(move || { thread::spawn(move || {
tsfn.call(true, ThreadsafeFunctionCallMode::Blocking); cb.call(true, ThreadsafeFunctionCallMode::Blocking);
}); });
Ok(()) Ok(())
} }
#[napi] #[napi]
pub fn threadsafe_function_fatal_mode_error(cb: JsFunction) -> Result<()> { pub fn threadsafe_function_fatal_mode_error(
let tsfn: ThreadsafeFunction<bool, ErrorStrategy::Fatal> = cb: ThreadsafeFunction<bool, String, false>,
cb.create_threadsafe_function(0, |_ctx| { ) -> Result<()> {
Err::<Vec<JsBoolean>, Error>(Error::new(
Status::GenericFailure,
"Generic tsfn error".to_owned(),
))
})?;
thread::spawn(move || { thread::spawn(move || {
tsfn.call(true, ThreadsafeFunctionCallMode::Blocking); cb.call_with_return_value(true, ThreadsafeFunctionCallMode::Blocking, |ret, _| {
ret.map(|_| ())
});
}); });
Ok(()) Ok(())
} }
@ -77,8 +72,8 @@ pub fn threadsafe_function_fatal_mode_error(cb: JsFunction) -> Result<()> {
fn threadsafe_function_closure_capture(func: JsFunction) -> napi::Result<()> { fn threadsafe_function_closure_capture(func: JsFunction) -> napi::Result<()> {
let str = "test"; let str = "test";
let tsfn: ThreadsafeFunction<()> = func let tsfn: ThreadsafeFunction<()> = func
.create_threadsafe_function(0, move |_| { .create_threadsafe_function(move |_| {
println!("{}", str); // str is NULL at this point println!("Captured in ThreadsafeFunction {}", str); // str is NULL at this point
Ok(Vec::<JsString>::new()) Ok(Vec::<JsString>::new())
}) })
.unwrap(); .unwrap();
@ -89,13 +84,12 @@ fn threadsafe_function_closure_capture(func: JsFunction) -> napi::Result<()> {
} }
#[napi] #[napi]
pub fn tsfn_call_with_callback(func: JsFunction) -> napi::Result<()> { pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Result<()> {
let tsfn: ThreadsafeFunction<()> =
func.create_threadsafe_function(0, move |_| Ok(Vec::<JsString>::new()))?;
tsfn.call_with_return_value( tsfn.call_with_return_value(
Ok(()), Ok(()),
ThreadsafeFunctionCallMode::NonBlocking, ThreadsafeFunctionCallMode::NonBlocking,
|value: String| { |value: Result<String>, _| {
let value = value.expect("Failed to retrieve value from JS");
println!("{}", value); println!("{}", value);
assert_eq!(value, "ReturnFromJavaScriptRawCallback".to_owned()); assert_eq!(value, "ReturnFromJavaScriptRawCallback".to_owned());
Ok(()) Ok(())
@ -105,12 +99,11 @@ pub fn tsfn_call_with_callback(func: JsFunction) -> napi::Result<()> {
} }
#[napi(ts_return_type = "Promise<void>")] #[napi(ts_return_type = "Promise<void>")]
pub fn tsfn_async_call(env: Env, func: JsFunction) -> napi::Result<Object> { pub fn tsfn_async_call(env: Env, func: Function<(u32, u32, u32), String>) -> napi::Result<Object> {
let tsfn: ThreadsafeFunction<()> = let tsfn = func.build_threadsafe_function().build()?;
func.create_threadsafe_function(0, move |_| Ok(vec![0u32, 1u32, 2u32]))?;
env.spawn_future(async move { env.spawn_future(async move {
let msg: String = tsfn.call_async(Ok(())).await?; let msg = tsfn.call_async((0, 1, 2)).await?;
assert_eq!(msg, "ReturnFromJavaScriptRawCallback".to_owned()); assert_eq!(msg, "ReturnFromJavaScriptRawCallback".to_owned());
Ok(()) Ok(())
}) })
@ -124,7 +117,7 @@ pub fn accept_threadsafe_function(func: ThreadsafeFunction<u32>) {
} }
#[napi] #[napi]
pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction<u32, ErrorStrategy::Fatal>) { pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction<u32, (), false>) {
thread::spawn(move || { thread::spawn(move || {
func.call(1, ThreadsafeFunctionCallMode::NonBlocking); func.call(1, ThreadsafeFunctionCallMode::NonBlocking);
}); });
@ -141,15 +134,17 @@ pub fn accept_threadsafe_function_tuple_args(func: ThreadsafeFunction<(u32, bool
} }
#[napi] #[napi]
pub async fn tsfn_return_promise(func: ThreadsafeFunction<u32>) -> Result<u32> { pub async fn tsfn_return_promise(func: ThreadsafeFunction<u32, Promise<u32>>) -> Result<u32> {
let val = func.call_async::<Promise<u32>>(Ok(1)).await?.await?; let val = func.call_async(Ok(1)).await?.await?;
Ok(val + 2) Ok(val + 2)
} }
#[napi] #[napi]
pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction<u32>) -> Result<u32> { pub async fn tsfn_return_promise_timeout(
func: ThreadsafeFunction<u32, Promise<u32>>,
) -> Result<u32> {
use tokio::time::{self, Duration}; use tokio::time::{self, Duration};
let promise = func.call_async::<Promise<u32>>(Ok(1)).await?; let promise = func.call_async(Ok(1)).await?;
let sleep = time::sleep(Duration::from_nanos(1)); let sleep = time::sleep(Duration::from_nanos(1));
tokio::select! { tokio::select! {
_ = sleep => { _ = sleep => {
@ -162,6 +157,6 @@ pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction<u32>) -> Resul
} }
#[napi] #[napi]
pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction<u32>) -> napi::Result<u32> { pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction<u32, Promise<u32>>) -> napi::Result<u32> {
tsfn.call_async::<Promise<u32>>(Ok(42)).await?.await tsfn.call_async(Ok(42)).await?.await
} }

View file

@ -2,7 +2,7 @@ use std::thread::spawn;
use napi::{ use napi::{
bindgen_prelude::*, bindgen_prelude::*,
threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode},
}; };
#[macro_use] #[macro_use]
@ -126,7 +126,7 @@ impl ChildReference {
#[napi] #[napi]
pub fn leaking_func(env: Env, func: JsFunction) -> napi::Result<()> { pub fn leaking_func(env: Env, func: JsFunction) -> napi::Result<()> {
let mut tsfn: ThreadsafeFunction<String> = let mut tsfn: ThreadsafeFunction<String> =
func.create_threadsafe_function(0, |mut ctx: ThreadSafeCallContext<String>| { func.create_threadsafe_function(|mut ctx: ThreadsafeCallContext<String>| {
ctx.env.adjust_external_memory(ctx.value.len() as i64)?; ctx.env.adjust_external_memory(ctx.value.len() as i64)?;
ctx ctx
.env .env

View file

@ -533,6 +533,7 @@ __metadata:
cross-env: "npm:7.0.3" cross-env: "npm:7.0.3"
electron: "npm:^29.0.1" electron: "npm:^29.0.1"
lodash: "npm:^4.17.21" lodash: "npm:^4.17.21"
rxjs: "npm:^7.8.1"
sinon: "npm:^17.0.1" sinon: "npm:^17.0.1"
vite: "npm:^5.0.12" vite: "npm:^5.0.12"
vite-plugin-node-polyfills: "npm:^0.19.0" vite-plugin-node-polyfills: "npm:^0.19.0"