diff --git a/bench/src/async_compute.rs b/bench/src/async_compute.rs index df0158ea..148ef394 100644 --- a/bench/src/async_compute.rs +++ b/bench/src/async_compute.rs @@ -37,7 +37,7 @@ fn bench_threadsafe_function(ctx: CallContext) -> Result { let tsfn = ctx.env.create_threadsafe_function( &callback, 0, - |mut ctx: ThreadSafeCallContext<(usize, Ref)>| { + |mut ctx: ThreadsafeCallContext<(usize, Ref)>| { ctx .env .create_uint32(ctx.value.0 as u32) diff --git a/bench/src/get_set_property.rs b/bench/src/get_set_property.rs index 96779789..5a0219b5 100644 --- a/bench/src/get_set_property.rs +++ b/bench/src/get_set_property.rs @@ -40,7 +40,7 @@ impl FromStr for LineJoin { } pub fn register_js(exports: &mut JsObject, env: &Env) -> Result<()> { - let test_class = env.define_class( + let test_class = env.define_class::( "TestClass", test_class_constructor, &[ diff --git a/bench/src/lib.rs b/bench/src/lib.rs index 01d38976..19d3c7ca 100644 --- a/bench/src/lib.rs +++ b/bench/src/lib.rs @@ -1,4 +1,5 @@ #![allow(clippy::uninlined_format_args)] +#![allow(deprecated)] #[macro_use] extern crate napi_derive; diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 4e853151..477c2fd4 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -212,6 +212,7 @@ static KNOWN_TYPES: Lazy> = La ("External", ("ExternalObject<{}>", false, false)), ("unknown", ("unknown", false, false)), ("Unknown", ("unknown", false, false)), + ("UnknownReturnValue", ("unknown", false, false)), ("JsUnknown", ("unknown", false, false)), ("This", ("this", false, false)), ("Rc", ("{}", false, false)), @@ -338,6 +339,7 @@ pub fn ty_to_ts_type( generic_ty, index == 1 && is_generic_function_type(&rust_ty), false, + // index == 2 is for ThreadsafeFunction with ErrorStrategy is_generic_function_type(&rust_ty), )) .map(|(mut ty, is_optional)| { @@ -346,6 +348,11 @@ pub fn ty_to_ts_type( } (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, }) .collect::>() @@ -408,15 +415,22 @@ pub fn ty_to_ts_type( { ts_ty = Some((t, false)); } else if rust_ty == TSFN_RUST_TY { - let fatal_tsfn = match args.get(1) { - Some((arg, _)) => arg == "Fatal", + let fatal_tsfn = match args.last() { + Some((arg, _)) => arg == "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 { - Some((format!("({}) => any", args), false)) + Some((format!("({fn_args}) => {return_ty}"), false)) } else { - Some((format!("(err: Error | null, {}) => any", args), false)) + Some(( + format!("(err: Error | null, {fn_args}) => {return_ty}"), + false, + )) }; } else { // there should be runtime registered type in else diff --git a/crates/napi/src/async_work.rs b/crates/napi/src/async_work.rs index 7a9a43e3..eebd3d95 100644 --- a/crates/napi/src/async_work.rs +++ b/crates/napi/src/async_work.rs @@ -114,11 +114,9 @@ unsafe extern "C" fn complete( let value = match value_ptr { Ok(v) => { let output = unsafe { v.assume_init() }; - work - .inner_task - .resolve(unsafe { Env::from_raw(env) }, output) + work.inner_task.resolve(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 { match check_status!(status) @@ -144,7 +142,7 @@ unsafe extern "C" fn complete( } }; } - 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); } let delete_status = unsafe { sys::napi_delete_async_work(env, napi_async_work) }; diff --git a/crates/napi/src/bindgen_runtime/js_values/date.rs b/crates/napi/src/bindgen_runtime/js_values/date.rs index 689432fc..13fd4a73 100644 --- a/crates/napi/src/bindgen_runtime/js_values/date.rs +++ b/crates/napi/src/bindgen_runtime/js_values/date.rs @@ -32,7 +32,7 @@ impl ValidateNapiValue for DateTime { impl ToNapiValue for NaiveDateTime { unsafe fn to_napi_value(env: sys::napi_env, val: NaiveDateTime) -> Result { 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!( unsafe { sys::napi_create_date(env, millis_since_epoch_utc, &mut ptr) }, @@ -145,11 +145,14 @@ impl FromNapiValue for DateTime { let milliseconds_since_epoch_utc = milliseconds_since_epoch_utc as i64; let timestamp_seconds = milliseconds_since_epoch_utc / 1_000; - let naive = NaiveDateTime::from_timestamp_opt( + let naive = DateTime::from_timestamp( timestamp_seconds, (milliseconds_since_epoch_utc % 1_000 * 1_000_000) as u32, ) .ok_or_else(|| Error::new(Status::DateExpected, "Found invalid date".to_owned()))?; - Ok(DateTime::::from_naive_utc_and_offset(naive, Utc)) + Ok(DateTime::::from_naive_utc_and_offset( + naive.naive_utc(), + Utc, + )) } } diff --git a/crates/napi/src/bindgen_runtime/js_values/function.rs b/crates/napi/src/bindgen_runtime/js_values/function.rs index 53f572c0..eab1b2e4 100644 --- a/crates/napi/src/bindgen_runtime/js_values/function.rs +++ b/crates/napi/src/bindgen_runtime/js_values/function.rs @@ -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; 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>; } +impl JsValuesTupleIntoVec for T { + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn into_vec(self, env: sys::napi_env) -> Result> { + Ok(vec![unsafe { + ::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 + 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> { + #[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 { + #[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. /// 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. /// 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) value: sys::napi_value, pub(crate) _args: std::marker::PhantomData, @@ -23,9 +98,7 @@ pub struct Function<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> { _scope: std::marker::PhantomData<&'scope ()>, } -impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName - for Function<'scope, Args, Return> -{ +impl<'scope, Args: JsValuesTupleIntoVec, Return> TypeName for Function<'scope, Args, Return> { fn type_name() -> &'static str { "Function" } @@ -35,17 +108,13 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> TypeName } } -impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> NapiRaw - for Function<'scope, Args, Return> -{ +impl<'scope, Args: JsValuesTupleIntoVec, Return> NapiRaw for Function<'scope, Args, Return> { unsafe fn raw(&self) -> sys::napi_value { self.value } } -impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> FromNapiValue - for Function<'scope, Args, Return> -{ +impl<'scope, Args: JsValuesTupleIntoVec, Return> FromNapiValue for Function<'scope, Args, Return> { unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result { Ok(Function { 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> { } +impl<'scope, Args: JsValuesTupleIntoVec, Return> Function<'scope, Args, Return> { + /// Get the name of the JavaScript function. + pub fn name(&self) -> Result { + 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> { + 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 { + 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 { + 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> { /// Call the JavaScript function. /// `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 args_ptr = args.into_vec(self.env)?; let mut raw_return = ptr::null_mut(); - check_pending_exception!( - self.env, + check_status!( unsafe { sys::napi_call_function( self.env, @@ -113,35 +240,66 @@ impl<'scope, Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'scope, )?; unsafe { Return::from_napi_value(self.env, raw_return) } } +} - /// Create a reference to the JavaScript function. - pub fn create_ref(&self) -> Result> { - 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, +pub struct ThreadsafeFunctionBuilder< + Args: JsValuesTupleIntoVec, + Return, + const Weak: bool = false, + const MaxQueueSize: usize = 0, +> { + pub(crate) env: sys::napi_env, + pub(crate) value: sys::napi_value, + _args: std::marker::PhantomData, + _return: std::marker::PhantomData, +} + +impl< + Args: JsValuesTupleIntoVec, + Return: FromNapiValue, + const Weak: bool, + const MaxQueueSize: usize, + > ThreadsafeFunctionBuilder +{ + pub fn weak( + self, + ) -> ThreadsafeFunctionBuilder { + ThreadsafeFunctionBuilder { env: self.env, + value: self.value, _args: std::marker::PhantomData, _return: std::marker::PhantomData, - }) + } + } + + pub fn max_queue_size( + self, + ) -> ThreadsafeFunctionBuilder { + ThreadsafeFunctionBuilder { + env: self.env, + value: self.value, + _args: std::marker::PhantomData, + _return: std::marker::PhantomData, + } + } + + pub fn build(self) -> Result> { + unsafe { ThreadsafeFunction::from_napi_value(self.env, self.value) } } } /// A reference to a JavaScript function. /// It can be used to outlive the scope of the function. -pub struct FunctionRef { +pub struct FunctionRef { pub(crate) inner: sys::napi_ref, pub(crate) env: sys::napi_env, _args: std::marker::PhantomData, _return: std::marker::PhantomData, } -unsafe impl Sync for FunctionRef {} +unsafe impl Sync for FunctionRef {} -impl FunctionRef { +impl FunctionRef { pub fn borrow_back<'scope>(&self, env: &'scope Env) -> Result> { let mut value = ptr::null_mut(); check_status!( @@ -158,14 +316,14 @@ impl FunctionRef Drop for FunctionRef { +impl Drop for FunctionRef { fn drop(&mut self) { let status = unsafe { sys::napi_delete_reference(self.env, self.inner) }; debug_assert_eq!(status, sys::Status::napi_ok, "Drop FunctionRef failed"); } } -impl TypeName for FunctionRef { +impl TypeName for FunctionRef { fn type_name() -> &'static str { "Function" } @@ -175,9 +333,7 @@ impl TypeName for FunctionRef } } -impl FromNapiValue - for FunctionRef -{ +impl FromNapiValue for FunctionRef { unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result { let mut reference = ptr::null_mut(); check_status!( @@ -198,6 +354,55 @@ impl 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(&self) -> Result { + 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(&self) -> Result { + unsafe { Args::from_slice_values(self.env.0, self.args) } + } + + /// Get the arguments Vec from the JavaScript function call. + pub fn arguments(&self) -> Result> { + self + .args + .iter() + .map(|arg| unsafe { ::from_napi_value(self.env.0, *arg) }) + .collect::>>() + } + + /// Get the `this` from the JavaScript function call. + pub fn this(&self) -> Result { + unsafe { This::from_napi_value(self.env.0, self.this) } + } +} + macro_rules! impl_call_apply { ($fn_call_name:ident, $fn_apply_name:ident, $($ident:ident),*) => { #[allow(non_snake_case, clippy::too_many_arguments)] @@ -205,7 +410,7 @@ macro_rules! impl_call_apply { &self, $($ident: $ident),* ) -> Result { - let raw_this = unsafe { Env::from_raw(self.0.env) } + let raw_this = Env::from_raw(self.0.env) .get_undefined() .map(|u| unsafe { u.raw() })?; @@ -284,7 +489,7 @@ impl JsFunction { } pub fn call0(&self) -> Result { - let raw_this = unsafe { Env::from_raw(self.0.env) } + let raw_this = Env::from_raw(self.0.env) .get_undefined() .map(|u| unsafe { u.raw() })?; diff --git a/crates/napi/src/bindgen_runtime/js_values/task.rs b/crates/napi/src/bindgen_runtime/js_values/task.rs index f4facfa5..e3fa8aba 100644 --- a/crates/napi/src/bindgen_runtime/js_values/task.rs +++ b/crates/napi/src/bindgen_runtime/js_values/task.rs @@ -3,7 +3,7 @@ use std::ptr; use std::rc::Rc; use std::sync::atomic::{AtomicPtr, AtomicU8, Ordering}; -use super::{FromNapiValue, ToNapiValue, TypeName}; +use super::{FromNapiValue, ToNapiValue, TypeName, Unknown}; use crate::{ 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(), status: task_status.clone(), }; - let js_env = unsafe { Env::from_raw(env) }; + let js_env = Env::from_raw(env); check_status!(unsafe { sys::napi_wrap( env, @@ -76,7 +76,10 @@ impl FromNapiValue for AbortSignal { ptr::null_mut(), ) })?; - signal.set_named_property("onabort", js_env.create_function("onabort", on_abort)?)?; + signal.set_named_property( + "onabort", + js_env.create_function::("onabort", on_abort)?, + )?; Ok(AbortSignal { raw_work: async_work_inner, raw_deferred: raw_promise, diff --git a/crates/napi/src/bindgen_runtime/mod.rs b/crates/napi/src/bindgen_runtime/mod.rs index 97605869..b3acec2d 100644 --- a/crates/napi/src/bindgen_runtime/mod.rs +++ b/crates/napi/src/bindgen_runtime/mod.rs @@ -35,7 +35,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked( _finalize_hint: *mut c_void, ) { let data: Box = 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(); unsafe { e.throw_into(env) }; return; diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index 31526e76..83eb58cc 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -7,9 +7,26 @@ use std::mem; use std::os::raw::{c_char, c_void}; 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")] 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::{ async_work::{self, AsyncWorkPromise}, check_status, @@ -19,21 +36,6 @@ use crate::{ 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(crate) static EMPTY_VEC: Vec = vec![]; @@ -58,7 +60,7 @@ impl From for Env { impl Env { #[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) } @@ -189,12 +191,7 @@ impl Env { pub fn create_string_latin1(&self, chars: &[u8]) -> Result { let mut raw_value = ptr::null_mut(); check_status!(unsafe { - sys::napi_create_string_latin1( - self.0, - chars.as_ptr() as *const _, - chars.len(), - &mut raw_value, - ) + sys::napi_create_string_latin1(self.0, chars.as_ptr().cast(), chars.len(), &mut 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. /// /// 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 { + pub fn create_function( + &self, + name: &str, + callback: Callback, + ) -> Result> { let mut raw_result = ptr::null_mut(); let len = name.len(); - let name = CString::new(name)?; check_status!(unsafe { sys::napi_create_function( self.0, - name.as_ptr(), + name.as_ptr().cast(), len, Some(callback), ptr::null_mut(), @@ -585,26 +585,29 @@ impl Env { ) })?; - Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) }) + unsafe { Function::::from_napi_value(self.0, raw_result) } } #[cfg(feature = "napi5")] - pub fn create_function_from_closure(&self, name: &str, callback: F) -> Result + pub fn create_function_from_closure( + &self, + name: &str, + callback: F, + ) -> Result> where - F: 'static + Fn(crate::CallContext<'_>) -> Result, - R: ToNapiValue, + Return: ToNapiValue, + F: 'static + Fn(FunctionCallContext) -> Result, { let closure_data_ptr = Box::into_raw(Box::new(callback)); let mut raw_result = ptr::null_mut(); let len = name.len(); - let name = CString::new(name)?; check_status!(unsafe { sys::napi_create_function( self.0, - name.as_ptr(), + name.as_ptr().cast(), len, - Some(trampoline::), + Some(trampoline::), closure_data_ptr.cast(), // We let it borrow the data here &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. @@ -740,12 +743,12 @@ impl Env { } /// Create JavaScript class - pub fn define_class( + pub fn define_class( &self, name: &str, constructor_cb: Callback, properties: &[Property], - ) -> Result { + ) -> Result> { let mut raw_result = ptr::null_mut(); let raw_properties = properties .iter() @@ -755,7 +758,7 @@ impl Env { check_status!(unsafe { sys::napi_define_class( self.0, - c_name.as_ptr() as *const c_char, + c_name.as_ptr().cast(), name.len(), Some(constructor_cb), 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)] @@ -1055,17 +1058,22 @@ impl Env { } #[cfg(feature = "napi4")] + #[deprecated( + since = "2.17.0", + note = "Please use `Function::build_threadsafe_function` instead" + )] + #[allow(deprecated)] pub fn create_threadsafe_function< T: Send, V: ToNapiValue, - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, >( &self, func: &JsFunction, - max_queue_size: usize, + _max_queue_size: usize, callback: R, ) -> Result> { - 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"))] @@ -1403,7 +1411,7 @@ unsafe extern "C" fn set_instance_finalize_callback( { let (value, callback) = unsafe { *Box::from_raw(finalize_data as *mut (TaggedObject, F)) }; 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 { value: value.object.unwrap(), hint, @@ -1425,7 +1433,7 @@ unsafe extern "C" fn raw_finalize_with_custom_callback( Finalize: FnOnce(Hint, Env), { 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")] @@ -1449,22 +1457,20 @@ unsafe extern "C" fn async_finalize( #[cfg(feature = "napi5")] pub(crate) unsafe extern "C" fn trampoline< - R: ToNapiValue, - F: Fn(crate::CallContext) -> Result, + Return: ToNapiValue, + F: Fn(FunctionCallContext) -> Result, >( raw_env: sys::napi_env, cb_info: sys::napi_callback_info, ) -> 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) = { - // 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 status = unsafe { + check_status!( + unsafe { sys::napi_get_cb_info( raw_env, cb_info, @@ -1473,45 +1479,45 @@ pub(crate) unsafe extern "C" fn trampoline< &mut raw_this, &mut closure_data_ptr, ) - }; - debug_assert!( - Status::from(status) == Status::Ok, - "napi_get_cb_info failed" - ); - + }, + "napi_get_cb_info failed" + ) + .and_then(|_| { // Arguments length greater than 4, resize the vector. if argc > 4 { raw_args = vec![ptr::null_mut(); argc]; - let status = unsafe { - sys::napi_get_cb_info( - raw_env, - cb_info, - &mut argc, - raw_args.as_mut_ptr(), - &mut raw_this, - &mut closure_data_ptr, - ) - }; - debug_assert!( - Status::from(status) == Status::Ok, + check_status!( + unsafe { + sys::napi_get_cb_info( + raw_env, + cb_info, + &mut argc, + raw_args.as_mut_ptr(), + &mut raw_this, + &mut closure_data_ptr, + ) + }, "napi_get_cb_info failed" - ); + )?; } else { unsafe { raw_args.set_len(argc) }; } - - (raw_this, raw_args, closure_data_ptr, argc) - }; - - let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); - let mut env = unsafe { Env::from_raw(raw_env) }; - let call_context = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc); - closure(call_context) - .and_then(|ret: R| unsafe { ::to_napi_value(env.0, ret) }) - .unwrap_or_else(|e| { - unsafe { JsError::from(e).throw_into(raw_env) }; - ptr::null_mut() + Ok((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 mut env = Env::from_raw(raw_env); + closure(FunctionCallContext { + env: &mut env, + this: raw_this, + args: raw_args.as_slice(), }) + }) + .and_then(|ret| unsafe { ::to_napi_value(raw_env, ret) }) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(raw_env) }; + ptr::null_mut() + }) } #[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 env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); raw_args .first() .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 env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); closure(env, unsafe { crate::bindgen_runtime::Object::from_raw_unchecked(raw_env, raw_this) }) diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs index ce4db4cc..db35ed08 100644 --- a/crates/napi/src/error.rs +++ b/crates/napi/src/error.rs @@ -20,7 +20,7 @@ pub type Result = std::result::Result>; /// Represent `JsError`. /// 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)` -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Error = Status> { pub status: S, pub reason: String, @@ -28,6 +28,17 @@ pub struct Error = Status> { pub(crate) maybe_raw: sys::napi_ref, } +impl> std::fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Error {{ status: {:?}, reason: {:?} }}", + self.status.as_ref(), + self.reason + ) + } +} + impl> ToNapiValue for Error { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { 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 error_msg = match value_type { 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!( $msg, format!( diff --git a/crates/napi/src/js_values/deferred.rs b/crates/napi/src/js_values/deferred.rs index 8cde0dc9..0e32e812 100644 --- a/crates/napi/src/js_values/deferred.rs +++ b/crates/napi/src/js_values/deferred.rs @@ -23,7 +23,7 @@ struct DeferredTrace(sys::napi_ref); #[cfg(feature = "deferred_trace")] impl DeferredTrace { fn new(raw_env: sys::napi_env) -> Result { - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); let reason = env.create_string("none").unwrap(); 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 { - let env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); let mut raw = ptr::null_mut(); check_status!( unsafe { sys::napi_get_reference_value(raw_env, self.0, &mut raw) }, @@ -210,7 +210,7 @@ extern "C" fn napi_resolve_deferred let deferred_data: Box> = unsafe { Box::from_raw(data.cast()) }; let result = deferred_data .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) }); if let Err(e) = result.and_then(|res| { diff --git a/crates/napi/src/js_values/function.rs b/crates/napi/src/js_values/function.rs index ce19fe6a..734131e5 100644 --- a/crates/napi/src/js_values/function.rs +++ b/crates/napi/src/js_values/function.rs @@ -4,12 +4,13 @@ use super::Value; #[cfg(feature = "napi4")] use crate::{ bindgen_runtime::ToNapiValue, - threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction}, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction}, }; use crate::{bindgen_runtime::TypeName, JsString}; use crate::{check_pending_exception, ValueType}; 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); impl TypeName for JsFunction { @@ -45,7 +46,7 @@ impl JsFunction { let raw_this = this .map(|v| unsafe { v.raw() }) .or_else(|| { - unsafe { Env::from_raw(self.0.env) } + Env::from_raw(self.0.env) .get_undefined() .ok() .map(|u| unsafe { u.raw() }) @@ -76,7 +77,7 @@ impl JsFunction { let raw_this = this .map(|v| unsafe { v.raw() }) .or_else(|| { - unsafe { Env::from_raw(self.0.env) } + Env::from_raw(self.0.env) .get_undefined() .ok() .map(|u| unsafe { u.raw() }) @@ -138,17 +139,24 @@ impl JsFunction { } #[cfg(feature = "napi4")] - pub fn create_threadsafe_function( + pub fn create_threadsafe_function< + T, + V, + Return, + F, + const ES: bool, + const Weak: bool, + const MaxQueueSize: usize, + >( &self, - max_queue_size: usize, callback: F, - ) -> Result> + ) -> Result> where T: 'static, + Return: crate::bindgen_runtime::FromNapiValue, V: ToNapiValue, - F: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, - ES: crate::threadsafe_function::ErrorStrategy::T, + F: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, { - ThreadsafeFunction::create(self.0.env, self.0.value, max_queue_size, callback) + ThreadsafeFunction::create(self.0.env, self.0.value, callback) } } diff --git a/crates/napi/src/js_values/global.rs b/crates/napi/src/js_values/global.rs index 12a58b94..61e37661 100644 --- a/crates/napi/src/js_values/global.rs +++ b/crates/napi/src/js_values/global.rs @@ -1,7 +1,8 @@ -use std::convert::TryInto; - use super::*; -use crate::{bindgen_runtime::FromNapiValue, Env}; +use crate::{ + bindgen_runtime::{FromNapiValue, Function}, + threadsafe_function::UnknownReturnValue, +}; pub struct JsGlobal(pub(crate) Value); @@ -21,56 +22,41 @@ impl FromNapiValue for JSON { impl JSON { pub fn stringify(&self, value: V) -> Result { - let func: JsFunction = self.get_named_property_unchecked("stringify")?; - let result = func - .call(None, &[value]) - .map(|ret| unsafe { ret.cast::() })?; - result.into_utf8()?.as_str().map(|s| s.to_owned()) + let func: Function = self.get_named_property_unchecked("stringify")?; + func.call(value) } } impl JsGlobal { - pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result { - let func: JsFunction = self.get_named_property_unchecked("setInterval")?; - func - .call( - None, - &[ - handler.into_unknown(), - unsafe { Env::from_raw(self.0.env) } - .create_double(interval)? - .into_unknown(), - ], - ) - .and_then(|ret| ret.try_into()) + pub fn set_interval( + &self, + handler: Function<(), UnknownReturnValue>, + interval: f64, + ) -> Result { + let func: Function<(Function<(), UnknownReturnValue>, f64), JsTimeout> = + self.get_named_property_unchecked("setInterval")?; + func.call((handler, interval)) } pub fn clear_interval(&self, timer: JsTimeout) -> Result { - let func: JsFunction = self.get_named_property_unchecked("clearInterval")?; - func - .call(None, &[timer.into_unknown()]) - .and_then(|ret| ret.try_into()) + let func: Function = + self.get_named_property_unchecked("clearInterval")?; + func.call(timer) } - pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result { - let func: JsFunction = self.get_named_property_unchecked("setTimeout")?; - func - .call( - None, - &[ - handler.into_unknown(), - unsafe { Env::from_raw(self.0.env) } - .create_double(interval)? - .into_unknown(), - ], - ) - .and_then(|ret| ret.try_into()) + pub fn set_timeout( + &self, + handler: Function<(), UnknownReturnValue>, + interval: f64, + ) -> Result { + let func: Function<(Function<(), UnknownReturnValue>, f64), JsTimeout> = + self.get_named_property_unchecked("setTimeout")?; + func.call((handler, interval)) } pub fn clear_timeout(&self, timer: JsTimeout) -> Result { - let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?; - func - .call(None, &[timer.into_unknown()]) - .and_then(|ret| ret.try_into()) + let func: Function = + self.get_named_property_unchecked("clearTimeout")?; + func.call(timer) } } diff --git a/crates/napi/src/js_values/mod.rs b/crates/napi/src/js_values/mod.rs index 2deeffa8..a660241a 100644 --- a/crates/napi/src/js_values/mod.rs +++ b/crates/napi/src/js_values/mod.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use std::convert::TryFrom; #[cfg(feature = "napi5")] use std::ffi::c_void; diff --git a/crates/napi/src/js_values/object.rs b/crates/napi/src/js_values/object.rs index 77507f00..eb1e05e9 100644 --- a/crates/napi/src/js_values/object.rs +++ b/crates/napi/src/js_values/object.rs @@ -76,7 +76,7 @@ unsafe extern "C" fn finalize_callback( let (value, callback, raw_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 env = unsafe { Env::from_raw(raw_env) }; + let env = Env::from_raw(raw_env); callback(FinalizeContext { env, value, hint }); if !raw_ref.is_null() { let status = unsafe { sys::napi_delete_reference(raw_env, raw_ref) }; diff --git a/crates/napi/src/js_values/ser.rs b/crates/napi/src/js_values/ser.rs index 75316d2f..9a53b2ba 100644 --- a/crates/napi/src/js_values/ser.rs +++ b/crates/napi/src/js_values/ser.rs @@ -308,7 +308,7 @@ impl ser::SerializeSeq for SeqSerializer { where 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.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -331,7 +331,7 @@ impl ser::SerializeTuple for SeqSerializer { where 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.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -354,7 +354,7 @@ impl ser::SerializeTupleStruct for SeqSerializer { where 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.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -377,7 +377,7 @@ impl ser::SerializeTupleVariant for SeqSerializer { where 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.current_index as _, JsUnknown(value.serialize(Ser::new(&env))?), @@ -405,7 +405,7 @@ impl ser::SerializeMap for MapSerializer { where 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))?); Ok(()) } @@ -414,7 +414,7 @@ impl ser::SerializeMap for MapSerializer { where 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( JsString(Value { env: self.key.0.env, @@ -435,7 +435,7 @@ impl ser::SerializeMap for MapSerializer { K: 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( JsString(key.serialize(Ser::new(&env))?), JsUnknown(value.serialize(Ser::new(&env))?), @@ -461,7 +461,7 @@ impl ser::SerializeStruct for StructSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.obj.0.env) }; + let env = Env::from_raw(self.obj.0.env); self .obj .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; @@ -482,7 +482,7 @@ impl ser::SerializeStructVariant for StructSerializer { where T: Serialize, { - let env = unsafe { Env::from_raw(self.obj.0.env) }; + let env = Env::from_raw(self.obj.0.env); self .obj .set_named_property(key, JsUnknown(value.serialize(Ser::new(&env))?))?; diff --git a/crates/napi/src/task.rs b/crates/napi/src/task.rs index 96ba80c9..1d5d9ce4 100644 --- a/crates/napi/src/task.rs +++ b/crates/napi/src/task.rs @@ -13,13 +13,15 @@ pub trait Task: Send + Sized { /// Into this method if `compute` return `Ok` fn resolve(&mut self, env: Env, output: Self::Output) -> Result; + #[allow(unused_variables)] /// Into this method if `compute` return `Err` - fn reject(&mut self, _env: Env, err: Error) -> Result { + fn reject(&mut self, env: Env, err: Error) -> Result { Err(err) } - // after resolve or reject - fn finally(&mut self, _env: Env) -> Result<()> { + #[allow(unused_variables)] + /// after resolve or reject + fn finally(&mut self, env: Env) -> Result<()> { Ok(()) } } diff --git a/crates/napi/src/threadsafe_function.rs b/crates/napi/src/threadsafe_function.rs index 41df54d4..c16e9097 100644 --- a/crates/napi/src/threadsafe_function.rs +++ b/crates/napi/src/threadsafe_function.rs @@ -1,21 +1,26 @@ #![allow(clippy::single_component_path_imports)] use std::convert::Into; -use std::ffi::CString; use std::marker::PhantomData; use std::os::raw::c_void; use std::ptr::{self, null_mut}; -use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; -use std::sync::{Arc, RwLock, RwLockWriteGuard, Weak}; +use std::sync::{ + self, + atomic::{AtomicBool, AtomicPtr, Ordering}, + Arc, RwLock, RwLockWriteGuard, +}; 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 = ThreadsafeCallContext; /// ThreadSafeFunction Context object /// the `value` is the value passed to `call` method -pub struct ThreadSafeCallContext { +pub struct ThreadsafeCallContext { pub env: Env, pub value: T, } @@ -36,68 +41,6 @@ impl From 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` 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::>(), - /// ) - /// }, - /// 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 { raw: AtomicPtr, aborted: RwLock, @@ -178,10 +121,10 @@ enum ThreadsafeFunctionCallVariant { WithCallback, } -struct ThreadsafeFunctionCallJsBackData { +struct ThreadsafeFunctionCallJsBackData { data: T, call_variant: ThreadsafeFunctionCallVariant, - callback: Box) -> Result<()>>, + callback: Box, Env) -> Result<()>>, } /// Communicate with the addon's main thread by invoking a JavaScript function from other threads. @@ -190,58 +133,70 @@ struct ThreadsafeFunctionCallJsBackData { /// An example of using `ThreadsafeFunction`: /// /// ```rust -/// #[macro_use] -/// extern crate napi_derive; -/// /// use std::thread; /// /// use napi::{ /// threadsafe_function::{ /// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode, /// }, -/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status, /// }; +/// use napi_derive::napi; /// -/// #[js_function(1)] -/// pub fn test_threadsafe_function(ctx: CallContext) -> Result { -/// let func = ctx.get::(0)?; -/// -/// let tsfn = -/// ctx -/// .env -/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext>| { -/// ctx.value -/// .iter() -/// .map(|v| ctx.env.create_uint32(*v)) -/// .collect::>>() -/// })?; -/// +/// #[napi] +/// pub fn call_threadsafe_function(callback: ThreadsafeFunction<(u32, bool, String), ()>) { /// let tsfn_cloned = tsfn.clone(); /// /// thread::spawn(move || { /// let output: Vec = vec![0, 1, 2, 3]; /// // 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 || { -/// let output: Vec = vec![3, 2, 1, 0]; -/// // It's okay to call a threadsafe function multiple times. -/// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking); +/// tsfn_cloned.call((3, false, "NAPI-RS".into())), ThreadsafeFunctionCallMode::NonBlocking); /// }); -/// -/// ctx.env.get_undefined() /// } /// ``` -pub struct ThreadsafeFunction { +pub struct ThreadsafeFunction< + T: 'static, + Return: FromNapiValue + 'static = Unknown, + const CalleeHandled: bool = true, + const Weak: bool = false, + const MaxQueueSize: usize = 0, +> { handle: Arc, - _phantom: PhantomData<(T, ES)>, + _phantom: PhantomData<(T, Return)>, } -unsafe impl Send for ThreadsafeFunction {} -unsafe impl Sync for ThreadsafeFunction {} +unsafe impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > Send for ThreadsafeFunction +{ +} -impl Clone for ThreadsafeFunction { +unsafe impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > Sync for ThreadsafeFunction +{ +} + +impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > Clone for ThreadsafeFunction +{ fn clone(&self) -> Self { self.handle.with_read_aborted(|aborted| { if aborted { @@ -256,86 +211,67 @@ impl Clone for ThreadsafeFunction { } } -impl JsValuesTupleIntoVec for T { - #[allow(clippy::not_unsafe_ptr_arg_deref)] - fn into_vec(self, env: sys::napi_env) -> Result> { - Ok(vec![unsafe { - ::to_napi_value(env, self)? - }]) - } -} - -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> { - #[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 FromNapiValue - for ThreadsafeFunction +impl< + T: JsValuesTupleIntoVec + 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > FromNapiValue for ThreadsafeFunction { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { - 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 ThreadsafeFunction { - /// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function) - /// for more information. +impl< + T: 'static, + Return: FromNapiValue, + const CalleeHandled: bool, + const Weak: bool, + const MaxQueueSize: usize, + > ThreadsafeFunction +{ + // 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< V: ToNapiValue, - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, >( env: sys::napi_env, func: sys::napi_value, - max_queue_size: usize, callback: R, ) -> Result { let mut async_resource_name = ptr::null_mut(); - let s = "napi_rs_threadsafe_function"; - let len = s.len(); - let s = CString::new(s)?; - check_status!(unsafe { - sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name) - })?; + static THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME: &str = "napi_rs_threadsafe_function"; + + #[cfg(feature = "experimental")] + { + 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 callback_ptr = Box::into_raw(Box::new(callback)); @@ -346,23 +282,35 @@ impl ThreadsafeFunction { func, ptr::null_mut(), async_resource_name, - max_queue_size, + MaxQueueSize, 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::), callback_ptr.cast(), - Some(call_js_cb::), + Some(call_js_cb::), &mut 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 { handle, _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) /// for more information. /// @@ -377,6 +325,10 @@ impl ThreadsafeFunction { }) } + #[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) /// for more information. pub fn unref(&mut self, env: &Env) -> Result<()> { @@ -395,6 +347,10 @@ impl ThreadsafeFunction { 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<()> { self.handle.with_write_aborted(|mut aborted_guard| { if !*aborted_guard { @@ -416,7 +372,9 @@ impl ThreadsafeFunction { } } -impl ThreadsafeFunction { +impl + ThreadsafeFunction +{ /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// for more information. pub fn call(&self, value: Result, mode: ThreadsafeFunctionCallMode) -> Status { @@ -432,7 +390,7 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::Direct, - callback: Box::new(|_d: Result| Ok(())), + callback: Box::new(|_d: Result, _| Ok(())), } }))) .cast(), @@ -443,7 +401,8 @@ impl ThreadsafeFunction { }) } - pub fn call_with_return_value Result<()>>( + /// Call the ThreadsafeFunction, and handle the return value with a callback + pub fn call_with_return_value, Env) -> Result<()>>( &self, value: Result, mode: ThreadsafeFunctionCallMode, @@ -461,9 +420,7 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { - d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb)) - }), + callback: Box::new(move |d: Result, env: Env| cb(d, env)), } }))) .cast(), @@ -475,8 +432,9 @@ impl ThreadsafeFunction { } #[cfg(feature = "tokio_rt")] - pub async fn call_async(&self, value: Result) -> Result { - let (sender, receiver) = tokio::sync::oneshot::channel::>(); + /// Call the ThreadsafeFunction, and handle the return value with in `async` way + pub async fn call_async(&self, value: Result) -> Result { + let (sender, receiver) = tokio::sync::oneshot::channel::>(); self.handle.with_read_aborted(|aborted| { if aborted { @@ -491,9 +449,9 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { + callback: Box::new(move |d: Result, _| { 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 // Not hiding the error would result in a napi_fatal_error call, it's safe to ignore it instead. .or(Ok(())) @@ -519,7 +477,9 @@ impl ThreadsafeFunction { } } -impl ThreadsafeFunction { +impl + ThreadsafeFunction +{ /// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function) /// for more information. pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status { @@ -534,7 +494,7 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::Direct, - callback: Box::new(|_d: Result| Ok(())), + callback: Box::new(|_d: Result, _: Env| Ok(())), })) .cast(), mode.into(), @@ -544,7 +504,8 @@ impl ThreadsafeFunction { }) } - pub fn call_with_return_value Result<()>>( + /// Call the ThreadsafeFunction, and handle the return value with a callback + pub fn call_with_return_value, Env) -> Result<()>>( &self, value: T, mode: ThreadsafeFunctionCallMode, @@ -561,9 +522,7 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { - d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb)) - }), + callback: Box::new(cb), })) .cast(), mode.into(), @@ -574,8 +533,9 @@ impl ThreadsafeFunction { } #[cfg(feature = "tokio_rt")] - pub async fn call_async(&self, value: T) -> Result { - let (sender, receiver) = tokio::sync::oneshot::channel::(); + /// Call the ThreadsafeFunction, and handle the return value with in `async` way + pub async fn call_async(&self, value: T) -> Result { + let (sender, receiver) = tokio::sync::oneshot::channel::(); self.handle.with_read_aborted(|aborted| { if aborted { @@ -588,15 +548,13 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: Result| { + callback: Box::new(move |d, _| { d.and_then(|d| { - D::from_napi_value(d.0.env, d.0.value).and_then(move |d| { - sender - .send(d) - // 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. - .or(Ok(())) - }) + sender + .send(d) + // 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. + .or(Ok(())) }) }), })) @@ -612,16 +570,15 @@ impl ThreadsafeFunction { } } -#[allow(unused_variables)] unsafe extern "C" fn thread_finalize_cb( - env: sys::napi_env, + #[allow(unused_variables)] env: sys::napi_env, finalize_data: *mut c_void, finalize_hint: *mut c_void, ) where - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, { - let handle_option = - unsafe { Weak::from_raw(finalize_data.cast::()).upgrade() }; + let handle_option: Option> = + unsafe { sync::Weak::from_raw(finalize_data.cast()).upgrade() }; if let Some(handle) = handle_option { handle.with_write_aborted(|mut aborted_guard| { @@ -635,29 +592,31 @@ unsafe extern "C" fn thread_finalize_cb( drop(unsafe { Box::::from_raw(finalize_hint.cast()) }); } -unsafe extern "C" fn call_js_cb( +unsafe extern "C" fn call_js_cb< + T: 'static, + Return: FromNapiValue, + V: ToNapiValue, + R, + const CalleeHandled: bool, +>( raw_env: sys::napi_env, js_callback: sys::napi_value, context: *mut c_void, data: *mut c_void, ) where - R: 'static + Send + FnMut(ThreadSafeCallContext) -> Result>, - ES: ErrorStrategy::T, + R: 'static + Send + FnMut(ThreadsafeCallContext) -> Result>, { // env and/or callback can be null when shutting down if raw_env.is_null() || js_callback.is_null() { 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 { - match ES::VALUE { - ErrorStrategy::CalleeHandled::VALUE => { - *Box::>>::from_raw(data.cast()) - } - ErrorStrategy::Fatal::VALUE => Ok(*Box::>::from_raw( - data.cast(), - )), + if CalleeHandled { + *Box::>>::from_raw(data.cast()) + } else { + Ok(*Box::>::from_raw(data.cast())) } }; @@ -665,8 +624,8 @@ unsafe extern "C" fn call_js_cb( unsafe { sys::napi_get_undefined(raw_env, &mut recv) }; let ret = val.and_then(|v| { - (ctx)(ThreadSafeCallContext { - env: unsafe { Env::from_raw(raw_env) }, + (callback)(ThreadsafeCallContext { + env: Env::from_raw(raw_env), value: v.data, }) .map(|ret| (ret, v.call_variant, v.callback)) @@ -680,7 +639,7 @@ unsafe extern "C" fn call_js_cb( let values = values .into_iter() .map(|v| unsafe { ToNapiValue::to_napi_value(raw_env, v) }); - let args: Result> = if ES::VALUE == ErrorStrategy::CalleeHandled::VALUE { + let args: Result> = if CalleeHandled { let mut js_null = ptr::null_mut(); unsafe { sys::napi_get_null(raw_env, &mut js_null) }; ::core::iter::once(Ok(js_null)).chain(values).collect() @@ -699,62 +658,45 @@ unsafe extern "C" fn call_js_cb( &mut return_value, ) }, - Err(e) => match ES::VALUE { - ErrorStrategy::Fatal::VALUE => unsafe { - sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) - }, - ErrorStrategy::CalleeHandled::VALUE => unsafe { - sys::napi_call_function( - raw_env, - recv, - js_callback, - 1, - [JsError::from(e).into_value(raw_env)].as_mut_ptr(), - &mut return_value, - ) - }, - }, + Err(e) => { + if CalleeHandled { + unsafe { sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)) } + } else { + unsafe { + sys::napi_call_function( + raw_env, + recv, + js_callback, + 1, + [JsError::from(e).into_value(raw_env)].as_mut_ptr(), + &mut return_value, + ) + } + } + } }; if let ThreadsafeFunctionCallVariant::WithCallback = call_variant { // throw Error in JavaScript callback let callback_arg = if status == sys::Status::napi_pending_exception { let mut exception = ptr::null_mut(); status = unsafe { sys::napi_get_and_clear_last_exception(raw_env, &mut exception) }; - Err( - JsUnknown(crate::Value { - env: raw_env, - value: exception, - value_type: crate::ValueType::Unknown, - }) - .into(), - ) + let mut error_reference = ptr::null_mut(); + unsafe { sys::napi_create_reference(raw_env, exception, 1, &mut error_reference) }; + Err(Error { + maybe_raw: error_reference, + status: Status::from(status), + reason: "".to_owned(), + }) } else { - Ok(JsUnknown(crate::Value { - env: raw_env, - value: return_value, - value_type: crate::ValueType::Unknown, - })) + unsafe { Return::from_napi_value(raw_env, return_value) } }; - if let Err(err) = callback(callback_arg) { - let message = format!( - "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, - ) - }; + if let Err(err) = callback(callback_arg, Env::from_raw(raw_env)) { + unsafe { sys::napi_fatal_exception(raw_env, JsError::from(err).into_value(raw_env)) }; } } 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)) }, Err(e) => unsafe { @@ -783,27 +725,27 @@ unsafe extern "C" fn call_js_cb( assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception); } else { 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(); assert_eq!( unsafe { sys::napi_create_string_utf8( raw_env, - error_code_string.as_ptr() as *const _, + error_code_string.as_ptr().cast(), error_code_string.len(), &mut error_code_value, ) }, 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(); assert_eq!( unsafe { sys::napi_create_string_utf8( raw_env, - error_msg.as_ptr() as *const _, - error_msg.len(), + ERROR_MSG.as_ptr().cast(), + ERROR_MSG.len(), &mut error_msg_value, ) }, @@ -823,99 +765,6 @@ unsafe extern "C" fn call_js_cb( } } -/// 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!( - "" - )] - /// ``` - } - #[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; impl TypeName for UnknownReturnValue { diff --git a/examples/napi-compat-mode/__tests__/function.spec.ts b/examples/napi-compat-mode/__tests__/function.spec.ts index 0c8e30a5..d1546ec4 100644 --- a/examples/napi-compat-mode/__tests__/function.spec.ts +++ b/examples/napi-compat-mode/__tests__/function.spec.ts @@ -36,7 +36,7 @@ test('should be able to create function from closure', (t) => { for (let i = 0; i < 100; i++) { t.is( bindings.testCreateFunctionFromClosure()( - ...Array.from({ length: i }).map((_, i) => i), + ...Array.from({ length: i }, (_, i) => i), ), `arguments length: ${i}`, ) diff --git a/examples/napi-compat-mode/src/class.rs b/examples/napi-compat-mode/src/class.rs index 7a2460b5..484f31f0 100644 --- a/examples/napi-compat-mode/src/class.rs +++ b/examples/napi-compat-mode/src/class.rs @@ -1,13 +1,16 @@ 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 { value: i32, } #[js_function(1)] -fn create_test_class(ctx: CallContext) -> Result { +fn create_test_class(ctx: CallContext) -> Result> { let add_count_method = Property::new("addCount")?.with_method(add_count); let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); let renew_wrapped = Property::new("renewWrapped")?.with_method(renew_wrapped); @@ -56,7 +59,7 @@ fn renew_wrapped(ctx: CallContext) -> Result { } #[js_function(1)] -fn new_test_class(ctx: CallContext) -> Result { +fn new_test_class(ctx: CallContext) -> Result { let add_count_method = Property::new("addCount")?.with_method(add_count); let add_native_count = Property::new("addNativeCount")?.with_method(add_native_count); let properties = vec![add_count_method, add_native_count]; @@ -65,7 +68,7 @@ fn new_test_class(ctx: CallContext) -> Result { .env .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<()> { diff --git a/examples/napi-compat-mode/src/function.rs b/examples/napi-compat-mode/src/function.rs index c51bc9f5..6e62e0b8 100644 --- a/examples/napi-compat-mode/src/function.rs +++ b/examples/napi-compat-mode/src/function.rs @@ -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)] pub fn call_function(ctx: CallContext) -> Result { @@ -44,15 +46,16 @@ pub fn call_function_error(ctx: CallContext) -> Result { } #[js_function(0)] -pub fn test_create_function_from_closure(ctx: CallContext) -> Result { +pub fn test_create_function_from_closure(ctx: CallContext) -> Result> { ctx .env .create_function_from_closure("functionFromClosure", move |ctx| { - if ctx.length != 0 { - let max: u32 = ctx.get(ctx.length - 1)?; - assert_eq!(max, ctx.length as u32 - 1); + if ctx.length() != 0 { + let args = ctx.arguments::()?; + 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())) }) } diff --git a/examples/napi-compat-mode/src/global.rs b/examples/napi-compat-mode/src/global.rs index 6dcff50d..b4443ba6 100644 --- a/examples/napi-compat-mode/src/global.rs +++ b/examples/napi-compat-mode/src/global.rs @@ -1,10 +1,10 @@ use std::convert::TryInto; -use napi::{CallContext, JsFunction, JsNumber, JsObject, JsTimeout, JsUndefined, Result}; +use napi::{CallContext, JsNumber, JsObject, JsTimeout, JsUndefined, Result}; #[js_function(2)] pub fn set_timeout(ctx: CallContext) -> Result { - let handler: JsFunction = ctx.get(0)?; + let handler = ctx.get(0)?; let timeout: JsNumber = ctx.get(1)?; ctx .env diff --git a/examples/napi-compat-mode/src/lib.rs b/examples/napi-compat-mode/src/lib.rs index 74c89192..d0d5b220 100644 --- a/examples/napi-compat-mode/src/lib.rs +++ b/examples/napi-compat-mode/src/lib.rs @@ -1,5 +1,6 @@ #![allow(unused_variables)] #![allow(clippy::uninlined_format_args)] +#![allow(deprecated)] #[macro_use] extern crate napi_derive; diff --git a/examples/napi-compat-mode/src/napi4/mod.rs b/examples/napi-compat-mode/src/napi4/mod.rs index 27b375d1..33c1840f 100644 --- a/examples/napi-compat-mode/src/napi4/mod.rs +++ b/examples/napi-compat-mode/src/napi4/mod.rs @@ -1,4 +1,4 @@ -use napi::{Env, JsObject, Property, Result}; +use napi::{bindgen_prelude::Unknown, Env, JsObject, Property, Result}; mod deferred; 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("testDeferred", deferred::test_deferred)?; - let obj = env.define_class( + let obj = env.define_class::( "A", constructor, &[ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index d6b71e9a..3b9bd06e 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -251,7 +251,7 @@ Generated by [AVA](https://avajs.dev). ␊ 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␊ ␊ @@ -310,6 +310,8 @@ Generated by [AVA](https://avajs.dev). ␊ export function bufferPassThrough(buf: Buffer): Promise␊ ␊ + export function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void␊ + ␊ export interface C {␊ 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 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␊ ␊ @@ -639,11 +641,11 @@ Generated by [AVA](https://avajs.dev). ␊ 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␊ ␊ @@ -653,15 +655,15 @@ Generated by [AVA](https://avajs.dev). ␊ export function toJsObj(): object␊ ␊ - export function tsfnAsyncCall(func: (...args: any[]) => any): Promise␊ + export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise␊ ␊ - 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␊ + export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise): Promise␊ ␊ - export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise␊ + export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise): Promise␊ ␊ - export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise␊ + export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise): Promise␊ ␊ export function tsRename(a: { foo: number }): string[]␊ ␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index ab45356b..1a6b9320 100644 Binary files a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap differ diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index 8a3e6fe6..5c0cacb5 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -19,6 +19,7 @@ Generated by [AVA](https://avajs.dev). 'cross-env', 'electron', 'lodash', + 'rxjs', 'sinon', 'vite', 'vite-plugin-node-polyfills', diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index d4241d4d..2b1be196 100644 Binary files a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap differ diff --git a/examples/napi/__tests__/tsfn-error.cjs b/examples/napi/__tests__/tsfn-error.cjs index 6007bfa1..aecafb21 100644 --- a/examples/napi/__tests__/tsfn-error.cjs +++ b/examples/napi/__tests__/tsfn-error.cjs @@ -1,5 +1,5 @@ -import('../index.cjs').then( - ({ threadsafeFunctionFatalModeError }) => { - return threadsafeFunctionFatalModeError(() => {}) - }, -) +const { threadsafeFunctionFatalModeError } = require('../index.cjs') + +threadsafeFunctionFatalModeError(() => { + return false +}) diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 76e68131..d10a9244 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -2,6 +2,7 @@ import { exec } from 'node:child_process' import { join } from 'node:path' import { fileURLToPath } from 'node:url' +import { Subject, take } from 'rxjs' import { spy } from 'sinon' import { @@ -170,6 +171,7 @@ import { throwSyntaxError, type AliasedStruct, returnObjectOnlyToJs, + buildThreadsafeFunctionFromFunction, } from '../index.cjs' import { test } from './test.framework.js' @@ -968,7 +970,7 @@ BigIntTest('from i128 i64', (t) => { t.is(bigintFromI128(), BigInt('-100')) }) -Napi4Test('call thread safe function', (t) => { +Napi4Test('call ThreadsafeFunction', (t) => { let i = 0 let value = 0 return new Promise((resolve) => { @@ -980,14 +982,14 @@ Napi4Test('call thread safe function', (t) => { resolve() t.is( 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) => { threadsafeFunctionThrowError(reject) }) @@ -995,7 +997,7 @@ Napi4Test('throw error from thread safe function', async (t) => { t.is(err?.message, 'ThrowFromNative') }) -Napi4Test('thread safe function closure capture data', (t) => { +Napi4Test('ThreadsafeFunction closure capture data', (t) => { return new Promise((resolve) => { threadsafeFunctionClosureCapture(() => { resolve() @@ -1024,7 +1026,7 @@ Napi4Test('throw error from thread safe function fatal mode', (t) => { t.is(code, 1) const stderrMsg = stderr.toString('utf8') console.info(stderrMsg) - t.true(stderrMsg.includes(`Error: Generic tsfn error`)) + t.true(stderrMsg.includes(`Error: Failed to convert JavaScript value`)) resolve() }) }) @@ -1060,8 +1062,7 @@ Napi4Test('call ThreadsafeFunction with callback', async (t) => { Napi4Test('async call ThreadsafeFunction', async (t) => { await t.notThrowsAsync(() => - tsfnAsyncCall((err, arg1, arg2, arg3) => { - t.is(err, null) + tsfnAsyncCall((arg1, arg2, arg3) => { t.is(arg1, 0) t.is(arg2, 1) t.is(arg3, 2) @@ -1163,6 +1164,20 @@ Napi4Test('object only from js', (t) => { }) }) +Napi4Test('build ThreadsafeFunction from Function', (t) => { + const subject = new Subject() + 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) => { t.is(await promiseInEither(1), false) t.is(await promiseInEither(20), true) diff --git a/examples/napi/electron.cjs b/examples/napi/electron.cjs index 0d35c543..4f7c9952 100644 --- a/examples/napi/electron.cjs +++ b/examples/napi/electron.cjs @@ -85,7 +85,7 @@ async function main() { assert( 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()) } diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index eb2b6fb3..2cf9a4bd 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -398,6 +398,7 @@ module.exports.bigintFromI128 = nativeBinding.bigintFromI128 module.exports.bigintFromI64 = nativeBinding.bigintFromI64 module.exports.bigintGetU64AsString = nativeBinding.bigintGetU64AsString module.exports.bufferPassThrough = nativeBinding.bufferPassThrough +module.exports.buildThreadsafeFunctionFromFunction = nativeBinding.buildThreadsafeFunctionFromFunction module.exports.call0 = nativeBinding.call0 module.exports.call1 = nativeBinding.call1 module.exports.call2 = nativeBinding.call2 diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index 19f89483..b4959e23 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -241,7 +241,7 @@ export function acceptSlice(fixture: Uint8Array): bigint 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 @@ -300,6 +300,8 @@ export function bigintGetU64AsString(bi: bigint): string export function bufferPassThrough(buf: Buffer): Promise +export function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void + export interface C { 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 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 @@ -629,11 +631,11 @@ export function testSerdeRoundtrip(data: any): any 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 @@ -643,15 +645,15 @@ export function throwSyntaxError(error: string, code?: string | undefined | null export function toJsObj(): object -export function tsfnAsyncCall(func: (...args: any[]) => any): Promise +export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise -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 +export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise): Promise -export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise +export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise): Promise -export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise +export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise): Promise export function tsRename(a: { foo: number }): string[] diff --git a/examples/napi/package.json b/examples/napi/package.json index 098f9a69..acc66a10 100644 --- a/examples/napi/package.json +++ b/examples/napi/package.json @@ -24,6 +24,7 @@ "cross-env": "7.0.3", "electron": "^29.0.1", "lodash": "^4.17.21", + "rxjs": "^7.8.1", "sinon": "^17.0.1", "vite": "^5.0.12", "vite-plugin-node-polyfills": "^0.19.0", diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs index 87156629..86f1d1dd 100644 --- a/examples/napi/src/callback.rs +++ b/examples/napi/src/callback.rs @@ -2,7 +2,7 @@ use std::{env, format}; use napi::{ bindgen_prelude::*, - threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, JsUnknown, }; @@ -65,7 +65,7 @@ fn callback_return_promise Result>( if ret.is_promise()? { let p = Promise::::from_unknown(ret)?; let fn_out_tsfn: ThreadsafeFunction = fn_out - .create_threadsafe_function(0, |ctx: ThreadSafeCallContext| Ok(vec![ctx.value]))?; + .create_threadsafe_function(|ctx: ThreadsafeCallContext| Ok(vec![ctx.value]))?; env .execute_tokio_future( async move { diff --git a/examples/napi/src/date.rs b/examples/napi/src/date.rs index 584e6d15..e0566d2e 100644 --- a/examples/napi/src/date.rs +++ b/examples/napi/src/date.rs @@ -15,7 +15,7 @@ fn chrono_date_to_millis(input: chrono::DateTime) -> i64 { #[napi] fn chrono_date_add_1_minute(input: chrono::DateTime) -> chrono::DateTime { - input + Duration::minutes(1) + Duration::try_minutes(1).map(|d| input + d).unwrap() } #[napi(object)] @@ -26,7 +26,7 @@ pub struct Dates { #[napi] pub fn chrono_native_date_time(date: chrono::NaiveDateTime) -> i64 { - date.timestamp_millis() + date.and_utc().timestamp_millis() } #[napi] diff --git a/examples/napi/src/enum.rs b/examples/napi/src/enum.rs index 7192662a..444f4065 100644 --- a/examples/napi/src/enum.rs +++ b/examples/napi/src/enum.rs @@ -19,6 +19,7 @@ pub enum Status { Ready, } +#[allow(clippy::enum_variant_names)] #[napi(string_enum = "lowercase")] pub enum StringEnum { VariantOne, diff --git a/examples/napi/src/function.rs b/examples/napi/src/function.rs index f456a425..737c5836 100644 --- a/examples/napi/src/function.rs +++ b/examples/napi/src/function.rs @@ -1,5 +1,8 @@ +#![allow(deprecated)] + use napi::{ bindgen_prelude::{ClassInstance, Function, FunctionRef}, + threadsafe_function::ThreadsafeFunctionCallMode, Env, JsFunction, JsObject, Result, }; @@ -74,3 +77,30 @@ pub fn reference_as_callback( ) -> Result { 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::() + .build()?; + + std::thread::spawn(move || { + tsfn_weak.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking); + }); + + Ok(()) +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index b5192452..4d8f0133 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -3,6 +3,7 @@ #![allow(clippy::disallowed_names)] #![allow(clippy::uninlined_format_args)] #![allow(clippy::new_without_default)] +#![allow(deprecated)] #[macro_use] extern crate napi_derive; diff --git a/examples/napi/src/threadsafe_function.rs b/examples/napi/src/threadsafe_function.rs index 0bbeb2c6..a3238358 100644 --- a/examples/napi/src/threadsafe_function.rs +++ b/examples/napi/src/threadsafe_function.rs @@ -2,14 +2,12 @@ use std::{thread, time::Duration}; use napi::{ bindgen_prelude::*, - threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}, - JsBoolean, JsString, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode, UnknownReturnValue}, + JsString, }; #[napi] -pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?; +pub fn call_threadsafe_function(tsfn: ThreadsafeFunction) -> Result<()> { for n in 0..100 { let tsfn = tsfn.clone(); thread::spawn(move || { @@ -20,9 +18,9 @@ pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> { } #[napi] -pub fn call_long_threadsafe_function(callback: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?; +pub fn call_long_threadsafe_function( + tsfn: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { for n in 0..10 { thread::sleep(Duration::from_millis(100)); @@ -33,11 +31,11 @@ pub fn call_long_threadsafe_function(callback: JsFunction) -> Result<()> { } #[napi] -pub fn threadsafe_function_throw_error(cb: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?; +pub fn threadsafe_function_throw_error( + cb: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { - tsfn.call( + cb.call( Err(Error::new( Status::GenericFailure, "ThrowFromNative".to_owned(), @@ -49,26 +47,23 @@ pub fn threadsafe_function_throw_error(cb: JsFunction) -> Result<()> { } #[napi] -pub fn threadsafe_function_fatal_mode(cb: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?; +pub fn threadsafe_function_fatal_mode( + cb: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { - tsfn.call(true, ThreadsafeFunctionCallMode::Blocking); + cb.call(true, ThreadsafeFunctionCallMode::Blocking); }); Ok(()) } #[napi] -pub fn threadsafe_function_fatal_mode_error(cb: JsFunction) -> Result<()> { - let tsfn: ThreadsafeFunction = - cb.create_threadsafe_function(0, |_ctx| { - Err::, Error>(Error::new( - Status::GenericFailure, - "Generic tsfn error".to_owned(), - )) - })?; +pub fn threadsafe_function_fatal_mode_error( + cb: ThreadsafeFunction, +) -> Result<()> { thread::spawn(move || { - tsfn.call(true, ThreadsafeFunctionCallMode::Blocking); + cb.call_with_return_value(true, ThreadsafeFunctionCallMode::Blocking, |ret, _| { + ret.map(|_| ()) + }); }); Ok(()) } @@ -77,8 +72,8 @@ pub fn threadsafe_function_fatal_mode_error(cb: JsFunction) -> Result<()> { fn threadsafe_function_closure_capture(func: JsFunction) -> napi::Result<()> { let str = "test"; let tsfn: ThreadsafeFunction<()> = func - .create_threadsafe_function(0, move |_| { - println!("{}", str); // str is NULL at this point + .create_threadsafe_function(move |_| { + println!("Captured in ThreadsafeFunction {}", str); // str is NULL at this point Ok(Vec::::new()) }) .unwrap(); @@ -89,13 +84,12 @@ fn threadsafe_function_closure_capture(func: JsFunction) -> napi::Result<()> { } #[napi] -pub fn tsfn_call_with_callback(func: JsFunction) -> napi::Result<()> { - let tsfn: ThreadsafeFunction<()> = - func.create_threadsafe_function(0, move |_| Ok(Vec::::new()))?; +pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Result<()> { tsfn.call_with_return_value( Ok(()), ThreadsafeFunctionCallMode::NonBlocking, - |value: String| { + |value: Result, _| { + let value = value.expect("Failed to retrieve value from JS"); println!("{}", value); assert_eq!(value, "ReturnFromJavaScriptRawCallback".to_owned()); Ok(()) @@ -105,12 +99,11 @@ pub fn tsfn_call_with_callback(func: JsFunction) -> napi::Result<()> { } #[napi(ts_return_type = "Promise")] -pub fn tsfn_async_call(env: Env, func: JsFunction) -> napi::Result { - let tsfn: ThreadsafeFunction<()> = - func.create_threadsafe_function(0, move |_| Ok(vec![0u32, 1u32, 2u32]))?; +pub fn tsfn_async_call(env: Env, func: Function<(u32, u32, u32), String>) -> napi::Result { + let tsfn = func.build_threadsafe_function().build()?; 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()); Ok(()) }) @@ -124,7 +117,7 @@ pub fn accept_threadsafe_function(func: ThreadsafeFunction) { } #[napi] -pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction) { +pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction) { thread::spawn(move || { func.call(1, ThreadsafeFunctionCallMode::NonBlocking); }); @@ -141,15 +134,17 @@ pub fn accept_threadsafe_function_tuple_args(func: ThreadsafeFunction<(u32, bool } #[napi] -pub async fn tsfn_return_promise(func: ThreadsafeFunction) -> Result { - let val = func.call_async::>(Ok(1)).await?.await?; +pub async fn tsfn_return_promise(func: ThreadsafeFunction>) -> Result { + let val = func.call_async(Ok(1)).await?.await?; Ok(val + 2) } #[napi] -pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction) -> Result { +pub async fn tsfn_return_promise_timeout( + func: ThreadsafeFunction>, +) -> Result { use tokio::time::{self, Duration}; - let promise = func.call_async::>(Ok(1)).await?; + let promise = func.call_async(Ok(1)).await?; let sleep = time::sleep(Duration::from_nanos(1)); tokio::select! { _ = sleep => { @@ -162,6 +157,6 @@ pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction) -> Resul } #[napi] -pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction) -> napi::Result { - tsfn.call_async::>(Ok(42)).await?.await +pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction>) -> napi::Result { + tsfn.call_async(Ok(42)).await?.await } diff --git a/memory-testing/src/lib.rs b/memory-testing/src/lib.rs index ce61119a..a6f2c9e3 100644 --- a/memory-testing/src/lib.rs +++ b/memory-testing/src/lib.rs @@ -2,7 +2,7 @@ use std::thread::spawn; use napi::{ bindgen_prelude::*, - threadsafe_function::{ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; #[macro_use] @@ -126,7 +126,7 @@ impl ChildReference { #[napi] pub fn leaking_func(env: Env, func: JsFunction) -> napi::Result<()> { let mut tsfn: ThreadsafeFunction = - func.create_threadsafe_function(0, |mut ctx: ThreadSafeCallContext| { + func.create_threadsafe_function(|mut ctx: ThreadsafeCallContext| { ctx.env.adjust_external_memory(ctx.value.len() as i64)?; ctx .env diff --git a/yarn.lock b/yarn.lock index f802d97f..150f596a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -533,6 +533,7 @@ __metadata: cross-env: "npm:7.0.3" electron: "npm:^29.0.1" lodash: "npm:^4.17.21" + rxjs: "npm:^7.8.1" sinon: "npm:^17.0.1" vite: "npm:^5.0.12" vite-plugin-node-polyfills: "npm:^0.19.0"