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

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

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

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

* depracate JsFunction

* CalleeHandled tsfn should handle Result in callback

* Pass env to call_with_return_value callback

* Fix compile

* clippy fix

* Fix electron test

* Function args

---------

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

View file

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

View file

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

View file

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

View file

@ -212,6 +212,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, (&'static str, bool, bool)>> = La
("External", ("ExternalObject<{}>", false, false)),
("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::<Vec<_>>()
@ -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

View file

@ -114,11 +114,9 @@ unsafe extern "C" fn complete<T: Task>(
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<T: Task>(
}
};
}
if let Err(e) = work.inner_task.finally(unsafe { Env::from_raw(env) }) {
if let Err(e) = work.inner_task.finally(Env::from_raw(env)) {
debug_assert!(false, "Panic in Task finally fn: {:?}", e);
}
let delete_status = unsafe { sys::napi_delete_async_work(env, napi_async_work) };

View file

@ -32,7 +32,7 @@ impl ValidateNapiValue for DateTime<Utc> {
impl ToNapiValue for NaiveDateTime {
unsafe fn to_napi_value(env: sys::napi_env, val: NaiveDateTime) -> Result<sys::napi_value> {
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<Utc> {
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::<Utc>::from_naive_utc_and_offset(naive, Utc))
Ok(DateTime::<Utc>::from_naive_utc_and_offset(
naive.naive_utc(),
Utc,
))
}
}

View file

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

View file

@ -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::<Unknown, Unknown>("onabort", on_abort)?,
)?;
Ok(AbortSignal {
raw_work: async_work_inner,
raw_deferred: raw_promise,

View file

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

View file

@ -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<u8> = vec![];
@ -58,7 +60,7 @@ impl From<sys::napi_env> 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<JsString> {
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<JsFunction> {
pub fn create_function<Args: JsValuesTupleIntoVec, Return>(
&self,
name: &str,
callback: Callback,
) -> Result<Function<Args, Return>> {
let mut raw_result = ptr::null_mut();
let 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::<Args, Return>::from_napi_value(self.0, raw_result) }
}
#[cfg(feature = "napi5")]
pub fn create_function_from_closure<R, F>(&self, name: &str, callback: F) -> Result<JsFunction>
pub fn create_function_from_closure<Args: JsValuesTupleIntoVec, Return, F>(
&self,
name: &str,
callback: F,
) -> Result<Function<Args, Return>>
where
F: 'static + Fn(crate::CallContext<'_>) -> Result<R>,
R: ToNapiValue,
Return: ToNapiValue,
F: 'static + Fn(FunctionCallContext) -> Result<Return>,
{
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::<R, F>),
Some(trampoline::<Return, F>),
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<Args: JsValuesTupleIntoVec>(
&self,
name: &str,
constructor_cb: Callback,
properties: &[Property],
) -> Result<JsFunction> {
) -> Result<Function<Args, Unknown>> {
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<T>) -> Result<Vec<V>>,
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
>(
&self,
func: &JsFunction,
max_queue_size: usize,
_max_queue_size: usize,
callback: R,
) -> Result<ThreadsafeFunction<T>> {
ThreadsafeFunction::create(self.0, func.0.value, max_queue_size, callback)
ThreadsafeFunction::create(self.0, func.0.value, callback)
}
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
@ -1403,7 +1411,7 @@ unsafe extern "C" fn set_instance_finalize_callback<T, Hint, F>(
{
let (value, callback) = unsafe { *Box::from_raw(finalize_data as *mut (TaggedObject<T>, F)) };
let 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<Hint, Finalize>(
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<Arg, F>(
#[cfg(feature = "napi5")]
pub(crate) unsafe extern "C" fn trampoline<
R: ToNapiValue,
F: Fn(crate::CallContext) -> Result<R>,
Return: ToNapiValue,
F: Fn(FunctionCallContext) -> Result<Return>,
>(
raw_env: sys::napi_env,
cb_info: sys::napi_callback_info,
) -> sys::napi_value {
use crate::CallContext;
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,16 +1479,15 @@ 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"
);
)
.and_then(|_| {
// Arguments length greater than 4, resize the vector.
if argc > 4 {
raw_args = vec![ptr::null_mut(); argc];
let status = unsafe {
check_status!(
unsafe {
sys::napi_get_cb_info(
raw_env,
cb_info,
@ -1491,23 +1496,24 @@ 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"
);
)?;
} else {
unsafe { raw_args.set_len(argc) };
}
(raw_this, raw_args, closure_data_ptr, argc)
};
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 = 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 { <R as ToNapiValue>::to_napi_value(env.0, ret) })
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 { <Return as ToNapiValue>::to_napi_value(raw_env, ret) })
.unwrap_or_else(|e| {
unsafe { JsError::from(e).throw_into(raw_env) };
ptr::null_mut()
@ -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)
})

View file

@ -20,7 +20,7 @@ pub type Result<T, S = Status> = std::result::Result<T, Error<S>>;
/// 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<S: AsRef<str> = Status> {
pub status: S,
pub reason: String,
@ -28,6 +28,17 @@ pub struct Error<S: AsRef<str> = Status> {
pub(crate) maybe_raw: sys::napi_ref,
}
impl<S: AsRef<str>> std::fmt::Debug for Error<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Error {{ status: {:?}, reason: {:?} }}",
self.status.as_ref(),
self.reason
)
}
}
impl<S: AsRef<str>> ToNapiValue for Error<S> {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
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!(

View file

@ -23,7 +23,7 @@ struct DeferredTrace(sys::napi_ref);
#[cfg(feature = "deferred_trace")]
impl DeferredTrace {
fn new(raw_env: sys::napi_env) -> Result<Self> {
let env = unsafe { Env::from_raw(raw_env) };
let env = Env::from_raw(raw_env);
let reason = env.create_string("none").unwrap();
let mut js_error = ptr::null_mut();
@ -42,7 +42,7 @@ impl DeferredTrace {
}
fn into_rejected(self, raw_env: sys::napi_env, err: Error) -> Result<sys::napi_value> {
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<Data: ToNapiValue, Resolver: FnOnce(Env) ->
let deferred_data: Box<DeferredData<Data, Resolver>> = 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| {

View file

@ -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<T, V, F, ES>(
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<ThreadsafeFunction<T, ES>>
) -> Result<ThreadsafeFunction<T, Return, ES, Weak, MaxQueueSize>>
where
T: 'static,
Return: crate::bindgen_runtime::FromNapiValue,
V: ToNapiValue,
F: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
ES: crate::threadsafe_function::ErrorStrategy::T,
F: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
{
ThreadsafeFunction::create(self.0.env, self.0.value, max_queue_size, callback)
ThreadsafeFunction::create(self.0.env, self.0.value, callback)
}
}

View file

@ -1,7 +1,8 @@
use std::convert::TryInto;
use super::*;
use 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<V: NapiRaw>(&self, value: V) -> Result<std::string::String> {
let func: JsFunction = self.get_named_property_unchecked("stringify")?;
let result = func
.call(None, &[value])
.map(|ret| unsafe { ret.cast::<JsString>() })?;
result.into_utf8()?.as_str().map(|s| s.to_owned())
let func: Function<V, std::string::String> = self.get_named_property_unchecked("stringify")?;
func.call(value)
}
}
impl JsGlobal {
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
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<JsTimeout> {
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<JsUndefined> {
let func: JsFunction = self.get_named_property_unchecked("clearInterval")?;
func
.call(None, &[timer.into_unknown()])
.and_then(|ret| ret.try_into())
let func: Function<JsTimeout, JsUndefined> =
self.get_named_property_unchecked("clearInterval")?;
func.call(timer)
}
pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
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<JsTimeout> {
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<JsUndefined> {
let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?;
func
.call(None, &[timer.into_unknown()])
.and_then(|ret| ret.try_into())
let func: Function<JsTimeout, JsUndefined> =
self.get_named_property_unchecked("clearTimeout")?;
func.call(timer)
}
}

View file

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

View file

@ -76,7 +76,7 @@ unsafe extern "C" fn finalize_callback<T, Hint, F>(
let (value, callback, raw_ref) =
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) };

View file

@ -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))?))?;

View file

@ -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<Self::JsValue>;
#[allow(unused_variables)]
/// Into this method if `compute` return `Err`
fn reject(&mut self, _env: Env, err: Error) -> Result<Self::JsValue> {
fn reject(&mut self, env: Env, err: Error) -> Result<Self::JsValue> {
Err(err)
}
// 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(())
}
}

View file

@ -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<T> = ThreadsafeCallContext<T>;
/// ThreadSafeFunction Context object
/// the `value` is the value passed to `call` method
pub struct ThreadSafeCallContext<T: 'static> {
pub struct ThreadsafeCallContext<T: 'static> {
pub env: Env,
pub value: T,
}
@ -36,68 +41,6 @@ impl From<ThreadsafeFunctionCallMode> for sys::napi_threadsafe_function_call_mod
}
}
type_level_enum! {
/// Type-level `enum` to express how to feed [`ThreadsafeFunction`] errors to
/// the inner [`JsFunction`].
///
/// ### Context
///
/// For callbacks that expect a `Result`-like kind of input, the convention is
/// to have the callback take an `error` parameter as its first parameter.
///
/// This way receiving a `Result<Args…>` can be modelled as follows:
///
/// - In case of `Err(error)`, feed that `error` entity as the first parameter
/// of the callback;
///
/// - Otherwise (in case of `Ok(_)`), feed `null` instead.
///
/// In pseudo-code:
///
/// ```rust,ignore
/// match result_args {
/// Ok(args) => {
/// let js_null = /* … */;
/// callback.call(
/// // this
/// None,
/// // args…
/// &iter::once(js_null).chain(args).collect::<Vec<_>>(),
/// )
/// },
/// Err(err) => callback.call(None, &[JsError::from(err)]),
/// }
/// ```
///
/// **Note that the `Err` case can stem from a failed conversion from native
/// values to js values when calling the callback!**
///
/// That's why:
///
/// > **[This][`ErrorStrategy::CalleeHandled`] is the default error strategy**.
///
/// In order to opt-out of it, [`ThreadsafeFunction`] has an optional second
/// generic parameter (of "kind" [`ErrorStrategy::T`]) that defines whether
/// this behavior ([`ErrorStrategy::CalleeHandled`]) or a non-`Result` one
/// ([`ErrorStrategy::Fatal`]) is desired.
pub enum ErrorStrategy {
/// Input errors (including conversion errors) are left for the callee to
/// handle:
///
/// The callee receives an extra `error` parameter (the first one), which is
/// `null` if no error occurred, and the error payload otherwise.
CalleeHandled,
/// Input errors (including conversion errors) are deemed fatal:
///
/// they can thus cause a `panic!` or abort the process.
///
/// The callee thus is not expected to have to deal with [that extra `error`
/// parameter][CalleeHandled], which is thus not added.
Fatal,
}
}
struct ThreadsafeFunctionHandle {
raw: AtomicPtr<sys::napi_threadsafe_function__>,
aborted: RwLock<bool>,
@ -178,10 +121,10 @@ enum ThreadsafeFunctionCallVariant {
WithCallback,
}
struct ThreadsafeFunctionCallJsBackData<T> {
struct ThreadsafeFunctionCallJsBackData<T, Return = Unknown> {
data: T,
call_variant: ThreadsafeFunctionCallVariant,
callback: Box<dyn FnOnce(Result<JsUnknown>) -> Result<()>>,
callback: Box<dyn FnOnce(Result<Return>, Env) -> Result<()>>,
}
/// Communicate with the addon's main thread by invoking a JavaScript function from other threads.
@ -190,58 +133,70 @@ struct ThreadsafeFunctionCallJsBackData<T> {
/// 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<JsUndefined> {
/// let func = ctx.get::<JsFunction>(0)?;
///
/// let tsfn =
/// ctx
/// .env
/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
/// ctx.value
/// .iter()
/// .map(|v| ctx.env.create_uint32(*v))
/// .collect::<Result<Vec<JsNumber>>>()
/// })?;
///
/// #[napi]
/// pub fn call_threadsafe_function(callback: ThreadsafeFunction<(u32, bool, String), ()>) {
/// let tsfn_cloned = tsfn.clone();
///
/// thread::spawn(move || {
/// let output: Vec<u32> = 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<u32> = 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<T: 'static, ES: ErrorStrategy::T = ErrorStrategy::CalleeHandled> {
pub struct ThreadsafeFunction<
T: 'static,
Return: FromNapiValue + 'static = Unknown,
const CalleeHandled: bool = true,
const Weak: bool = false,
const MaxQueueSize: usize = 0,
> {
handle: Arc<ThreadsafeFunctionHandle>,
_phantom: PhantomData<(T, ES)>,
_phantom: PhantomData<(T, Return)>,
}
unsafe impl<T: 'static, ES: ErrorStrategy::T> Send for ThreadsafeFunction<T, ES> {}
unsafe impl<T: 'static, ES: ErrorStrategy::T> Sync for ThreadsafeFunction<T, ES> {}
unsafe impl<
T: 'static,
Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> Send for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
}
impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> {
unsafe impl<
T: 'static,
Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> Sync for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
}
impl<
T: 'static,
Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> Clone for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
fn clone(&self) -> Self {
self.handle.with_read_aborted(|aborted| {
if aborted {
@ -256,86 +211,67 @@ impl<T: 'static, ES: ErrorStrategy::T> Clone for ThreadsafeFunction<T, ES> {
}
}
impl<T: ToNapiValue> JsValuesTupleIntoVec for T {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> {
Ok(vec![unsafe {
<T as ToNapiValue>::to_napi_value(env, self)?
}])
}
}
macro_rules! impl_js_value_tuple_to_vec {
($($ident:ident),*) => {
impl<$($ident: ToNapiValue),*> JsValuesTupleIntoVec for ($($ident,)*) {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn into_vec(self, env: sys::napi_env) -> Result<Vec<sys::napi_value>> {
#[allow(non_snake_case)]
let ($($ident,)*) = self;
Ok(vec![$(unsafe { <$ident as ToNapiValue>::to_napi_value(env, $ident)? }),*])
}
}
};
}
impl_js_value_tuple_to_vec!(A);
impl_js_value_tuple_to_vec!(A, B);
impl_js_value_tuple_to_vec!(A, B, C);
impl_js_value_tuple_to_vec!(A, B, C, D);
impl_js_value_tuple_to_vec!(A, B, C, D, E);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
impl_js_value_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
impl_js_value_tuple_to_vec!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y
);
impl_js_value_tuple_to_vec!(
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
);
impl<T: JsValuesTupleIntoVec + 'static, ES: ErrorStrategy::T> FromNapiValue
for ThreadsafeFunction<T, ES>
impl<
T: JsValuesTupleIntoVec + 'static,
Return: FromNapiValue,
const CalleeHandled: bool,
const Weak: bool,
const MaxQueueSize: usize,
> FromNapiValue for ThreadsafeFunction<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
Self::create(env, napi_val, 0, |ctx| ctx.value.into_vec(ctx.env.0))
Self::create(env, napi_val, |ctx| ctx.value.into_vec(ctx.env.0))
}
}
impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
/// 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<T, Return, { CalleeHandled }, { Weak }, { MaxQueueSize }>
{
// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
// for more information.
pub(crate) fn create<
V: ToNapiValue,
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
>(
env: sys::napi_env,
func: sys::napi_value,
max_queue_size: usize,
callback: R,
) -> Result<Self> {
let mut async_resource_name = ptr::null_mut();
let s = "napi_rs_threadsafe_function";
let len = s.len();
let s = CString::new(s)?;
static THREAD_SAFE_FUNCTION_ASYNC_RESOURCE_NAME: &str = "napi_rs_threadsafe_function";
#[cfg(feature = "experimental")]
{
check_status!(unsafe {
sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name)
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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
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::<T, V, R>),
callback_ptr.cast(),
Some(call_js_cb::<T, V, R, ES>),
Some(call_js_cb::<T, Return, V, R, CalleeHandled>),
&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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
})
}
#[deprecated(
since = "2.17.0",
note = "Please use `ThreadsafeFunction::clone` instead of manually decreasing the reference count"
)]
/// See [napi_unref_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_unref_threadsafe_function)
/// for more information.
pub fn unref(&mut self, env: &Env) -> Result<()> {
@ -395,6 +347,10 @@ impl<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
self.handle.with_read_aborted(|aborted| aborted)
}
#[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<T: 'static, ES: ErrorStrategy::T> ThreadsafeFunction<T, ES> {
}
}
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
impl<T: 'static, Return: FromNapiValue + 'static, const Weak: bool, const MaxQueueSize: usize>
ThreadsafeFunction<T, Return, true, { Weak }, { MaxQueueSize }>
{
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
/// for more information.
pub fn call(&self, value: Result<T>, mode: ThreadsafeFunctionCallMode) -> Status {
@ -432,7 +390,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
ThreadsafeFunctionCallJsBackData {
data,
call_variant: ThreadsafeFunctionCallVariant::Direct,
callback: Box::new(|_d: Result<JsUnknown>| Ok(())),
callback: Box::new(|_d: Result<Return>, _| Ok(())),
}
})))
.cast(),
@ -443,7 +401,8 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
})
}
pub fn call_with_return_value<D: FromNapiValue, F: 'static + FnOnce(D) -> Result<()>>(
/// Call the ThreadsafeFunction, and handle the return value with a callback
pub fn call_with_return_value<F: 'static + FnOnce(Result<Return>, Env) -> Result<()>>(
&self,
value: Result<T>,
mode: ThreadsafeFunctionCallMode,
@ -461,9 +420,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
ThreadsafeFunctionCallJsBackData {
data,
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| {
d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb))
}),
callback: Box::new(move |d: Result<Return>, env: Env| cb(d, env)),
}
})))
.cast(),
@ -475,8 +432,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
}
#[cfg(feature = "tokio_rt")]
pub async fn call_async<D: 'static + FromNapiValue>(&self, value: Result<T>) -> Result<D> {
let (sender, receiver) = tokio::sync::oneshot::channel::<Result<D>>();
/// Call the ThreadsafeFunction, and handle the return value with in `async` way
pub async fn call_async(&self, value: Result<T>) -> Result<Return> {
let (sender, receiver) = tokio::sync::oneshot::channel::<Result<Return>>();
self.handle.with_read_aborted(|aborted| {
if aborted {
@ -491,9 +449,9 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
ThreadsafeFunctionCallJsBackData {
data,
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| {
callback: Box::new(move |d: Result<Return>, _| {
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<T: 'static> ThreadsafeFunction<T, ErrorStrategy::CalleeHandled> {
}
}
impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
impl<T: 'static, Return: FromNapiValue + 'static, const Weak: bool, const MaxQueueSize: usize>
ThreadsafeFunction<T, Return, false, { Weak }, { MaxQueueSize }>
{
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
/// for more information.
pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status {
@ -534,7 +494,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
data: value,
call_variant: ThreadsafeFunctionCallVariant::Direct,
callback: Box::new(|_d: Result<JsUnknown>| Ok(())),
callback: Box::new(|_d: Result<Return>, _: Env| Ok(())),
}))
.cast(),
mode.into(),
@ -544,7 +504,8 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
})
}
pub fn call_with_return_value<D: FromNapiValue, F: 'static + FnOnce(D) -> Result<()>>(
/// Call the ThreadsafeFunction, and handle the return value with a callback
pub fn call_with_return_value<F: 'static + FnOnce(Result<Return>, Env) -> Result<()>>(
&self,
value: T,
mode: ThreadsafeFunctionCallMode,
@ -561,9 +522,7 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
data: value,
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| {
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<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
}
#[cfg(feature = "tokio_rt")]
pub async fn call_async<D: 'static + FromNapiValue>(&self, value: T) -> Result<D> {
let (sender, receiver) = tokio::sync::oneshot::channel::<D>();
/// Call the ThreadsafeFunction, and handle the return value with in `async` way
pub async fn call_async(&self, value: T) -> Result<Return> {
let (sender, receiver) = tokio::sync::oneshot::channel::<Return>();
self.handle.with_read_aborted(|aborted| {
if aborted {
@ -588,16 +548,14 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData {
data: value,
call_variant: ThreadsafeFunctionCallVariant::WithCallback,
callback: Box::new(move |d: Result<JsUnknown>| {
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(()))
})
})
}),
}))
.cast(),
@ -612,16 +570,15 @@ impl<T: 'static> ThreadsafeFunction<T, ErrorStrategy::Fatal> {
}
}
#[allow(unused_variables)]
unsafe extern "C" fn thread_finalize_cb<T: 'static, V: ToNapiValue, R>(
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<T>) -> Result<Vec<V>>,
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
{
let handle_option =
unsafe { Weak::from_raw(finalize_data.cast::<ThreadsafeFunctionHandle>()).upgrade() };
let handle_option: Option<Arc<ThreadsafeFunctionHandle>> =
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<T: 'static, V: ToNapiValue, R>(
drop(unsafe { Box::<R>::from_raw(finalize_hint.cast()) });
}
unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
unsafe extern "C" fn call_js_cb<
T: 'static,
Return: FromNapiValue,
V: ToNapiValue,
R,
const CalleeHandled: bool,
>(
raw_env: sys::napi_env,
js_callback: sys::napi_value,
context: *mut c_void,
data: *mut c_void,
) where
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<Vec<V>>,
ES: ErrorStrategy::T,
R: 'static + Send + FnMut(ThreadsafeCallContext<T>) -> Result<Vec<V>>,
{
// 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::<Result<ThreadsafeFunctionCallJsBackData<T>>>::from_raw(data.cast())
}
ErrorStrategy::Fatal::VALUE => Ok(*Box::<ThreadsafeFunctionCallJsBackData<T>>::from_raw(
data.cast(),
)),
if CalleeHandled {
*Box::<Result<ThreadsafeFunctionCallJsBackData<T, Return>>>::from_raw(data.cast())
} else {
Ok(*Box::<ThreadsafeFunctionCallJsBackData<T, Return>>::from_raw(data.cast()))
}
};
@ -665,8 +624,8 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
unsafe { sys::napi_get_undefined(raw_env, &mut recv) };
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<T: 'static, V: ToNapiValue, R, ES>(
let values = values
.into_iter()
.map(|v| unsafe { ToNapiValue::to_napi_value(raw_env, v) });
let args: Result<Vec<sys::napi_value>> = if ES::VALUE == ErrorStrategy::CalleeHandled::VALUE {
let args: Result<Vec<sys::napi_value>> = if CalleeHandled {
let mut js_null = ptr::null_mut();
unsafe { sys::napi_get_null(raw_env, &mut js_null) };
::core::iter::once(Ok(js_null)).chain(values).collect()
@ -699,11 +658,11 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
&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 {
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,
@ -712,49 +671,32 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
[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,
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(),
})
.into(),
)
} else {
Ok(JsUnknown(crate::Value {
env: raw_env,
value: return_value,
value_type: crate::ValueType::Unknown,
}))
};
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,
)
unsafe { Return::from_napi_value(raw_env, return_value) }
};
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<T: 'static, V: ToNapiValue, R, ES>(
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<T: 'static, V: ToNapiValue, R, ES>(
}
}
/// Helper
macro_rules! type_level_enum {(
$( #[doc = $doc:tt] )*
$pub:vis
enum $EnumName:ident {
$(
$( #[doc = $doc_variant:tt] )*
$Variant:ident
),* $(,)?
}
) => (type_level_enum! { // This requires the macro to be in scope when called.
with_docs! {
$( #[doc = $doc] )*
///
/// ### Type-level `enum`
///
/// Until `const_generics` can handle custom `enum`s, this pattern must be
/// implemented at the type level.
///
/// We thus end up with:
///
/// ```rust,ignore
/// #[type_level_enum]
#[doc = ::core::concat!(
" enum ", ::core::stringify!($EnumName), " {",
)]
$(
#[doc = ::core::concat!(
" ", ::core::stringify!($Variant), ",",
)]
)*
#[doc = " }"]
/// ```
///
#[doc = ::core::concat!(
"With [`", ::core::stringify!($EnumName), "::T`](#reexports) \
being the type-level \"enum type\":",
)]
///
/// ```rust,ignore
#[doc = ::core::concat!(
"<Param: ", ::core::stringify!($EnumName), "::T>"
)]
/// ```
}
#[allow(warnings)]
$pub mod $EnumName {
#[doc(no_inline)]
pub use $EnumName as T;
super::type_level_enum! {
with_docs! {
#[doc = ::core::concat!(
"See [`", ::core::stringify!($EnumName), "`]\
[super::", ::core::stringify!($EnumName), "]"
)]
}
pub trait $EnumName : __sealed::$EnumName + ::core::marker::Sized + 'static {
const VALUE: __value::$EnumName;
}
}
mod __sealed { pub trait $EnumName {} }
mod __value {
#[derive(Debug, PartialEq, Eq)]
pub enum $EnumName { $( $Variant ),* }
}
$(
$( #[doc = $doc_variant] )*
pub enum $Variant {}
impl __sealed::$EnumName for $Variant {}
impl $EnumName for $Variant {
const VALUE: __value::$EnumName = __value::$EnumName::$Variant;
}
impl $Variant {
pub const VALUE: __value::$EnumName = __value::$EnumName::$Variant;
}
)*
}
});(
with_docs! {
$( #[doc = $doc:expr] )*
}
$item:item
) => (
$( #[doc = $doc] )*
$item
)}
use type_level_enum;
pub struct UnknownReturnValue;
impl TypeName for UnknownReturnValue {

View file

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

View file

@ -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<JsFunction> {
fn create_test_class(ctx: CallContext) -> Result<Function<Unknown, Unknown>> {
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<JsUndefined> {
}
#[js_function(1)]
fn new_test_class(ctx: CallContext) -> Result<JsObject> {
fn new_test_class(ctx: CallContext) -> Result<Unknown> {
let add_count_method = Property::new("addCount")?.with_method(add_count);
let add_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<JsObject> {
.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<()> {

View file

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

View file

@ -1,10 +1,10 @@
use std::convert::TryInto;
use 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<JsTimeout> {
let handler: JsFunction = ctx.get(0)?;
let handler = ctx.get(0)?;
let timeout: JsNumber = ctx.get(1)?;
ctx
.env

View file

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

View file

@ -1,4 +1,4 @@
use napi::{Env, JsObject, Property, Result};
use napi::{bindgen_prelude::Unknown, Env, JsObject, Property, Result};
mod deferred;
mod 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::<Unknown>(
"A",
constructor,
&[

View file

@ -251,7 +251,7 @@ Generated by [AVA](https://avajs.dev).
export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void␊
export function 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<Buffer>
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<void>
@ -653,15 +655,15 @@ Generated by [AVA](https://avajs.dev).
export function toJsObj(): object␊
export function tsfnAsyncCall(func: (...args: any[]) => any): Promise<void>
export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise<void>
export function tsfnCallWithCallback(func: (...args: any[]) => any): void␊
export function tsfnCallWithCallback(tsfn: (err: Error | null, ) => string): void␊
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => any): Promise<number>
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise<number>
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise<number>
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsRename(a: { foo: number }): string[]␊

View file

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

View file

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

View file

@ -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<void>()
const fn = (a: number, b: number) => {
t.is(a, 1)
t.is(b, 2)
subject.next()
return a * b
}
buildThreadsafeFunctionFromFunction(fn)
return subject.pipe(take(3))
})
Napi4Test('promise in either', async (t) => {
t.is(await promiseInEither(1), false)
t.is(await promiseInEither(20), true)

View file

@ -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())
}

View file

@ -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

View file

@ -241,7 +241,7 @@ export function acceptSlice(fixture: Uint8Array): bigint
export function acceptThreadsafeFunction(func: (err: Error | null, arg: number) => any): void
export function 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<Buffer>
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<void>
@ -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<void>
export function tsfnAsyncCall(func: (arg0: number, arg1: number, arg2: number) => string): Promise<void>
export function tsfnCallWithCallback(func: (...args: any[]) => any): void
export function tsfnCallWithCallback(tsfn: (err: Error | null, ) => string): void
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => any): Promise<number>
export function tsfnReturnPromise(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => any): Promise<number>
export function tsfnReturnPromiseTimeout(func: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => any): Promise<number>
export function tsfnThrowFromJs(tsfn: (err: Error | null, arg: number) => Promise<number>): Promise<number>
export function tsRename(a: { foo: number }): string[]

View file

@ -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",

View file

@ -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<T: Fn() -> Result<JsUnknown>>(
if ret.is_promise()? {
let p = Promise::<String>::from_unknown(ret)?;
let fn_out_tsfn: ThreadsafeFunction<String> = fn_out
.create_threadsafe_function(0, |ctx: ThreadSafeCallContext<String>| Ok(vec![ctx.value]))?;
.create_threadsafe_function(|ctx: ThreadsafeCallContext<String>| Ok(vec![ctx.value]))?;
env
.execute_tokio_future(
async move {

View file

@ -15,7 +15,7 @@ fn chrono_date_to_millis(input: chrono::DateTime<Utc>) -> i64 {
#[napi]
fn chrono_date_add_1_minute(input: chrono::DateTime<Utc>) -> chrono::DateTime<Utc> {
input + Duration::minutes(1)
Duration::try_minutes(1).map(|d| input + d).unwrap()
}
#[napi(object)]
@ -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]

View file

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

View file

@ -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<u32> {
callback.borrow_back(&env)?.call((arg0, arg1))
}
#[napi]
pub fn build_threadsafe_function_from_function(callback: Function<(u32, u32), u32>) -> Result<()> {
let tsfn = callback.build_threadsafe_function().build()?;
std::thread::spawn(move || {
tsfn.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking);
});
let tsfn_max_queue_size_1 = callback
.build_threadsafe_function()
.max_queue_size::<1>()
.build()?;
std::thread::spawn(move || {
tsfn_max_queue_size_1.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking);
});
let tsfn_weak = callback
.build_threadsafe_function()
.weak::<true>()
.build()?;
std::thread::spawn(move || {
tsfn_weak.call((1, 2), ThreadsafeFunctionCallMode::NonBlocking);
});
Ok(())
}

View file

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

View file

@ -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<u32, ErrorStrategy::CalleeHandled> =
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?;
pub fn call_threadsafe_function(tsfn: ThreadsafeFunction<u32, UnknownReturnValue>) -> 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<u32, ErrorStrategy::CalleeHandled> =
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value + 1]))?;
pub fn call_long_threadsafe_function(
tsfn: ThreadsafeFunction<u32, UnknownReturnValue>,
) -> 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<bool, ErrorStrategy::CalleeHandled> =
cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?;
pub fn threadsafe_function_throw_error(
cb: ThreadsafeFunction<bool, UnknownReturnValue>,
) -> 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<bool, ErrorStrategy::Fatal> =
cb.create_threadsafe_function(0, |ctx| ctx.env.get_boolean(ctx.value).map(|v| vec![v]))?;
pub fn threadsafe_function_fatal_mode(
cb: ThreadsafeFunction<bool, UnknownReturnValue, false>,
) -> 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<bool, ErrorStrategy::Fatal> =
cb.create_threadsafe_function(0, |_ctx| {
Err::<Vec<JsBoolean>, Error>(Error::new(
Status::GenericFailure,
"Generic tsfn error".to_owned(),
))
})?;
pub fn threadsafe_function_fatal_mode_error(
cb: ThreadsafeFunction<bool, String, false>,
) -> 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::<JsString>::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::<JsString>::new()))?;
pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Result<()> {
tsfn.call_with_return_value(
Ok(()),
ThreadsafeFunctionCallMode::NonBlocking,
|value: String| {
|value: Result<String>, _| {
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<void>")]
pub fn tsfn_async_call(env: Env, func: JsFunction) -> napi::Result<Object> {
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<Object> {
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<u32>) {
}
#[napi]
pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction<u32, ErrorStrategy::Fatal>) {
pub fn accept_threadsafe_function_fatal(func: ThreadsafeFunction<u32, (), false>) {
thread::spawn(move || {
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<u32>) -> Result<u32> {
let val = func.call_async::<Promise<u32>>(Ok(1)).await?.await?;
pub async fn tsfn_return_promise(func: ThreadsafeFunction<u32, Promise<u32>>) -> Result<u32> {
let val = func.call_async(Ok(1)).await?.await?;
Ok(val + 2)
}
#[napi]
pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction<u32>) -> Result<u32> {
pub async fn tsfn_return_promise_timeout(
func: ThreadsafeFunction<u32, Promise<u32>>,
) -> Result<u32> {
use tokio::time::{self, Duration};
let promise = func.call_async::<Promise<u32>>(Ok(1)).await?;
let promise = func.call_async(Ok(1)).await?;
let sleep = time::sleep(Duration::from_nanos(1));
tokio::select! {
_ = sleep => {
@ -162,6 +157,6 @@ pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction<u32>) -> Resul
}
#[napi]
pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction<u32>) -> napi::Result<u32> {
tsfn.call_async::<Promise<u32>>(Ok(42)).await?.await
pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction<u32, Promise<u32>>) -> napi::Result<u32> {
tsfn.call_async(Ok(42)).await?.await
}

View file

@ -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<String> =
func.create_threadsafe_function(0, |mut ctx: ThreadSafeCallContext<String>| {
func.create_threadsafe_function(|mut ctx: ThreadsafeCallContext<String>| {
ctx.env.adjust_external_memory(ctx.value.len() as i64)?;
ctx
.env

View file

@ -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"