diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 3262caf9..bc0f35b2 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,5 +1,5 @@ use proc_macro2::Ident; -use syn::Attribute; +use syn::{Attribute, Type}; #[derive(Debug, Clone)] pub struct NapiFn { @@ -76,6 +76,7 @@ pub struct NapiImpl { pub name: Ident, pub js_name: String, pub items: Vec, + pub task_output_type: Option, } #[derive(Debug, Clone)] diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index c85d9dc7..323b3039 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -76,6 +76,13 @@ impl TryToTokens for NapiStruct { impl NapiStruct { fn gen_helper_mod(&self) -> TokenStream { + if crate::typegen::r#struct::TASK_STRUCTS.with(|t| { + println!("{:?}", t); + t.borrow().get(&self.name.to_string()).is_some() + }) { + return quote! {}; + } + let mod_name = Ident::new( &format!("__napi_helper__{}", self.name.to_string()), Span::call_site(), diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 7839c9f8..3b561faf 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -1,6 +1,6 @@ mod r#enum; mod r#fn; -mod r#struct; +pub(crate) mod r#struct; use std::collections::HashMap; @@ -35,6 +35,7 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("i16", "number"), ("i32", "number"), ("i64", "number"), + ("f64", "number"), ("u8", "number"), ("u16", "number"), ("u32", "number"), @@ -54,6 +55,9 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("Value", "any"), ("Map", "Record"), ("HashMap", "Record<{}, {}>"), + ("ArrayBuffer", "ArrayBuffer"), + ("DataView", "DataView"), + ("Date", "Date"), ("Buffer", "Buffer"), // TODO: Vec should be Buffer, now is Array ("Vec", "Array<{}>"), @@ -63,6 +67,12 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("Either3", "{} | {} | {}"), ("Either4", "{} | {} | {} | {}"), ("Either5", "{} | {} | {} | {} | {}"), + ("unknown", "unknown"), + ("null", "null"), + ("symbol", "symbol"), + ("external", "object"), + ("AsyncTaskAbortController", "AbortController"), + ("Function", "(...args: any[]) => any"), ]); map @@ -124,6 +134,15 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> String { if rust_ty == "Result" && is_return_ty { ts_ty = Some(args.first().unwrap().to_owned()); + } else if rust_ty == "AsyncTask" { + ts_ty = r#struct::TASK_STRUCTS.with(|t| { + let output_type = args.first().unwrap().to_owned(); + if let Some(o) = t.borrow().get(&output_type) { + Some(format!("Promise<{}>", o)) + } else { + Some("Promise".to_owned()) + } + }); } else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) { if known_ty.contains("{}") { ts_ty = Some(fill_ty(known_ty, args)); diff --git a/crates/backend/src/typegen/struct.rs b/crates/backend/src/typegen/struct.rs index 9943691c..7998e0a2 100644 --- a/crates/backend/src/typegen/struct.rs +++ b/crates/backend/src/typegen/struct.rs @@ -1,6 +1,13 @@ +use std::cell::RefCell; +use std::collections::HashMap; + use super::{ToTypeDef, TypeDef}; use crate::{ty_to_ts_type, NapiImpl, NapiStruct, NapiStructKind}; +thread_local! { + pub(crate) static TASK_STRUCTS: RefCell> = Default::default(); +} + impl ToTypeDef for NapiStruct { fn to_type_def(&self) -> TypeDef { TypeDef { @@ -17,6 +24,12 @@ impl ToTypeDef for NapiStruct { impl ToTypeDef for NapiImpl { fn to_type_def(&self) -> TypeDef { + if let Some(output_type) = &self.task_output_type { + TASK_STRUCTS.with(|t| { + t.borrow_mut() + .insert(self.js_name.clone(), ty_to_ts_type(output_type, false)); + }); + } TypeDef { kind: "impl".to_owned(), name: self.js_name.to_owned(), diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index fc4994b9..c0caa687 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -18,7 +18,7 @@ type-def = ["napi-derive-backend/type-def"] [dependencies] convert_case = "0.4" -napi-derive-backend = {version = "1", path = "../backend"} +napi-derive-backend = {version = "1.0.2", path = "../backend"} proc-macro2 = "1.0" quote = "1.0" syn = {version = "1.0", features = ["fold", "full", "extra-traits"]} diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index 69f3315c..cf002a7f 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -15,7 +15,7 @@ use napi_derive_backend::{ use proc_macro2::{Ident, TokenStream, TokenTree}; use quote::ToTokens; use syn::parse::{Parse, ParseStream, Result as SynResult}; -use syn::{Attribute, Signature, Visibility}; +use syn::{Attribute, Signature, Type, Visibility}; use crate::parser::attrs::{check_recorded_struct_for_impl, record_struct}; @@ -755,43 +755,52 @@ impl ConvertToAST for syn::ItemImpl { let mut struct_js_name = struct_name.to_string(); let mut items = vec![]; - + let mut task_output_type = None; for item in self.items.iter_mut() { - let method = match item { - syn::ImplItem::Method(m) => m, + if let Some(method) = match item { + syn::ImplItem::Method(m) => Some(m), + syn::ImplItem::Type(m) => { + if m.ident == *"JsValue" { + if let Type::Path(_) = &m.ty { + task_output_type = Some(m.ty.clone()); + } + } + None + } _ => { bail_span!(item, "unsupported impl item in #[napi]") } - }; - let opts = BindgenAttrs::find(&mut method.attrs)?; + } { + let opts = BindgenAttrs::find(&mut method.attrs)?; - // it'd better only care methods decorated with `#[napi]` attribute - if !opts.exists { - continue; - } - - if opts.constructor().is_some() { - struct_js_name = check_recorded_struct_for_impl(&struct_name, &opts)?; - } - - let vis = method.vis.clone(); - - match &vis { - Visibility::Public(_) => {} - _ => { - bail_span!(method.sig.ident, "only pub method supported by #[napi].",); + // it'd better only care methods decorated with `#[napi]` attribute + if !opts.exists { + continue; } + + if opts.constructor().is_some() { + struct_js_name = check_recorded_struct_for_impl(&struct_name, &opts)?; + } + + let vis = method.vis.clone(); + + match &vis { + Visibility::Public(_) => {} + _ => { + bail_span!(method.sig.ident, "only pub method supported by #[napi].",); + } + } + + let func = napi_fn_from_decl( + method.sig.clone(), + &opts, + method.attrs.clone(), + vis, + Some(&struct_name), + )?; + + items.push(func); } - - let func = napi_fn_from_decl( - method.sig.clone(), - &opts, - method.attrs.clone(), - vis, - Some(&struct_name), - )?; - - items.push(func); } Ok(Napi { @@ -800,6 +809,7 @@ impl ConvertToAST for syn::ItemImpl { name: struct_name, js_name: struct_js_name, items, + task_output_type, }), }) } diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index 3a6d69c9..7c132893 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -28,7 +28,7 @@ tokio_rt = ["tokio", "once_cell", "napi4"] [dependencies] ctor = "0.1" -napi-sys = {version = "1", path = "../sys"} +napi-sys = {version = "1.1.2", path = "../sys"} [target.'cfg(windows)'.dependencies] winapi = {version = "0.3.9", features = ["winuser", "minwindef", "ntdef", "libloaderapi"]} diff --git a/crates/napi/src/async_work.rs b/crates/napi/src/async_work.rs index d53bacc7..4684d3a0 100644 --- a/crates/napi/src/async_work.rs +++ b/crates/napi/src/async_work.rs @@ -1,11 +1,12 @@ use std::mem; -use std::os::raw::{c_char, c_void}; +use std::os::raw::c_void; use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::{ffi::CString, rc::Rc}; use crate::{ - check_status, - js_values::{NapiRaw, NapiValue}, - sys, Env, JsError, JsObject, Result, Task, + bindgen_runtime::ToNapiValue, check_status, js_values::NapiValue, sys, Env, JsError, JsObject, + Result, Task, }; struct AsyncWork { @@ -13,46 +14,53 @@ struct AsyncWork { deferred: sys::napi_deferred, value: Result>, napi_async_work: sys::napi_async_work, + abort: Rc, } -pub struct AsyncWorkPromise<'env> { - napi_async_work: sys::napi_async_work, +pub struct AsyncWorkPromise { + pub(crate) napi_async_work: sys::napi_async_work, raw_promise: sys::napi_value, - env: &'env Env, + pub(crate) deferred: sys::napi_deferred, + env: sys::napi_env, + // share with AsyncWork + pub(crate) abort: Rc, } -impl<'env> AsyncWorkPromise<'env> { +impl AsyncWorkPromise { pub fn promise_object(&self) -> JsObject { - unsafe { JsObject::from_raw_unchecked(self.env.0, self.raw_promise) } + unsafe { JsObject::from_raw_unchecked(self.env, self.raw_promise) } } - pub fn cancel(self) -> Result<()> { - check_status!(unsafe { sys::napi_cancel_async_work(self.env.0, self.napi_async_work) }) + pub fn cancel(&self) -> Result<()> { + // must be happened in the main thread, relaxed is enough + self.abort.store(true, Ordering::Relaxed); + check_status!(unsafe { sys::napi_cancel_async_work(self.env, self.napi_async_work) }) } } -pub fn run(env: &Env, task: T) -> Result> { +pub fn run( + env: sys::napi_env, + task: T, + abort_status: Option>, +) -> Result { let mut raw_resource = ptr::null_mut(); - check_status!(unsafe { sys::napi_create_object(env.0, &mut raw_resource) })?; + check_status!(unsafe { sys::napi_create_object(env, &mut raw_resource) })?; let mut raw_promise = ptr::null_mut(); let mut deferred = ptr::null_mut(); - check_status!(unsafe { sys::napi_create_promise(env.0, &mut deferred, &mut raw_promise) })?; - let mut raw_name = ptr::null_mut(); - let s = "napi_rs_async_work"; - check_status!(unsafe { - sys::napi_create_string_utf8(env.0, s.as_ptr() as *const c_char, s.len(), &mut raw_name) - })?; + check_status!(unsafe { sys::napi_create_promise(env, &mut deferred, &mut raw_promise) })?; + let task_abort = abort_status.unwrap_or_else(|| Rc::new(AtomicBool::new(false))); let result = Box::leak(Box::new(AsyncWork { inner_task: task, deferred, value: Ok(mem::MaybeUninit::zeroed()), napi_async_work: ptr::null_mut(), + abort: task_abort.clone(), })); check_status!(unsafe { sys::napi_create_async_work( - env.0, + env, raw_resource, - raw_name, + CString::new("napi_rs_async_work")?.as_ptr() as *mut _, Some(execute:: as unsafe extern "C" fn(env: sys::napi_env, data: *mut c_void)), Some( complete:: @@ -62,11 +70,13 @@ pub fn run(env: &Env, task: T) -> Result> { &mut result.napi_async_work, ) })?; - check_status!(unsafe { sys::napi_queue_async_work(env.0, result.napi_async_work) })?; + check_status!(unsafe { sys::napi_queue_async_work(env, result.napi_async_work) })?; Ok(AsyncWorkPromise { napi_async_work: result.napi_async_work, raw_promise, + deferred, env, + abort: task_abort, }) } @@ -78,11 +88,16 @@ unsafe impl Sync for AsyncWork {} /// So it actually could do nothing here, because `execute` function is called in the other thread mostly. unsafe extern "C" fn execute(_env: sys::napi_env, data: *mut c_void) { let mut work = Box::from_raw(data as *mut AsyncWork); + if work.abort.load(Ordering::Relaxed) { + return; + } let _ = mem::replace( &mut work.value, work.inner_task.compute().map(mem::MaybeUninit::new), ); - Box::leak(work); + if !work.abort.load(Ordering::Relaxed) { + Box::leak(work); + } } unsafe extern "C" fn complete( @@ -101,10 +116,13 @@ unsafe extern "C" fn complete( } Err(e) => work.inner_task.reject(Env::from_raw(env), e), }; - match check_status!(status).and_then(move |_| value) { + match check_status!(status) + .and_then(move |_| value) + .and_then(|v| ToNapiValue::to_napi_value(env, v)) + { Ok(v) => { - let status = sys::napi_resolve_deferred(env, deferred, v.raw()); - debug_assert!(status == sys::Status::napi_ok, "Reject promise failed"); + let status = sys::napi_resolve_deferred(env, deferred, v); + debug_assert!(status == sys::Status::napi_ok, "Resolve promise failed"); } Err(e) => { let status = sys::napi_reject_deferred(env, deferred, JsError::from(e).into_value(env)); diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 7f35f72c..f85a0f95 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -1,4 +1,4 @@ -use crate::{check_status, sys, Error, JsUnknown, Result, Status, ValueType}; +use crate::{check_status, sys, Error, JsUnknown, NapiRaw, NapiValue, Result, Status, ValueType}; use std::ptr; mod array; @@ -12,6 +12,7 @@ mod object; #[cfg(feature = "serde-json")] mod serde; mod string; +mod task; pub use array::*; pub use buffer::*; @@ -19,6 +20,7 @@ pub use either::*; pub use nil::*; pub use object::*; pub use string::*; +pub use task::*; #[cfg(feature = "latin1")] pub use string::latin1_string::*; @@ -36,9 +38,25 @@ pub trait ToNapiValue { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result; } -impl ToNapiValue for JsUnknown { +impl TypeName for JsUnknown { + fn type_name() -> &'static str { + "unknown" + } + + fn value_type() -> ValueType { + ValueType::Unknown + } +} + +impl ToNapiValue for T { unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result { - Ok(val.0.value) + Ok(NapiRaw::raw(&val)) + } +} + +impl FromNapiValue for T { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + Ok(T::from_raw_unchecked(env, napi_val)) } } @@ -49,16 +67,6 @@ pub trait FromNapiValue: Sized { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result; } -impl FromNapiValue for JsUnknown { - unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { - Ok(JsUnknown(crate::Value { - env, - value: napi_val, - value_type: crate::ValueType::Unknown, - })) - } -} - pub trait FromNapiRef { /// # Safety /// diff --git a/crates/napi/src/bindgen_runtime/js_values/task.rs b/crates/napi/src/bindgen_runtime/js_values/task.rs new file mode 100644 index 00000000..717072d3 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/task.rs @@ -0,0 +1,159 @@ +use std::ffi::c_void; +use std::ptr; +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; + +use super::{FromNapiValue, ToNapiValue, TypeName}; +use crate::{async_work, check_status, Env, Error, JsError, JsObject, NapiValue, Status, Task}; + +pub struct AsyncTask { + inner: T, + abort_controller: Option, +} + +impl TypeName for T { + fn type_name() -> &'static str { + "AsyncTask" + } + + fn value_type() -> crate::ValueType { + crate::ValueType::Object + } +} + +impl AsyncTask { + pub fn new(task: T) -> Self { + Self { + inner: task, + abort_controller: None, + } + } + + pub fn with_abort_controller(task: T, abort_controller: AsyncTaskAbortController) -> Self { + Self { + inner: task, + abort_controller: Some(abort_controller), + } + } +} + +/// https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController +pub struct AsyncTaskAbortController { + raw_work: Rc>, + raw_deferred: Rc>, + abort: Rc, +} + +impl FromNapiValue for AsyncTaskAbortController { + unsafe fn from_napi_value( + env: napi_sys::napi_env, + napi_val: napi_sys::napi_value, + ) -> crate::Result { + let raw_abort_controller = JsObject::from_raw_unchecked(env, napi_val); + let mut signal = raw_abort_controller.get_named_property::("signal")?; + let async_work_inner: Rc> = + Rc::new(AtomicPtr::new(ptr::null_mut())); + let raw_promise: Rc> = + Rc::new(AtomicPtr::new(ptr::null_mut())); + let abort_status = Rc::new(AtomicBool::new(false)); + let abort_controller = AsyncTaskAbortController { + raw_work: async_work_inner.clone(), + raw_deferred: raw_promise.clone(), + abort: abort_status.clone(), + }; + let js_env = Env::from_raw(env); + check_status!(napi_sys::napi_wrap( + env, + signal.0.value, + Box::into_raw(Box::new(abort_controller)) as *mut _, + Some(async_task_abort_controller_finalize), + ptr::null_mut(), + ptr::null_mut(), + ))?; + signal.set_named_property("onabort", js_env.create_function("onabort", on_abort)?)?; + Ok(AsyncTaskAbortController { + raw_work: async_work_inner, + raw_deferred: raw_promise, + abort: abort_status, + }) + } +} + +extern "C" fn on_abort( + env: napi_sys::napi_env, + callback_info: napi_sys::napi_callback_info, +) -> napi_sys::napi_value { + let mut this = ptr::null_mut(); + unsafe { + let get_cb_info_status = napi_sys::napi_get_cb_info( + env, + callback_info, + &mut 0, + ptr::null_mut(), + &mut this, + ptr::null_mut(), + ); + debug_assert_eq!( + get_cb_info_status, + napi_sys::Status::napi_ok, + "{}", + "Get callback info in AbortController abort callback failed" + ); + let mut async_task = ptr::null_mut(); + let status = napi_sys::napi_unwrap(env, this, &mut async_task); + debug_assert_eq!( + status, + napi_sys::Status::napi_ok, + "{}", + "Unwrap async_task from AbortSignal failed" + ); + let abort_controller = Box::leak(Box::from_raw(async_task as *mut AsyncTaskAbortController)); + let raw_async_work = abort_controller.raw_work.load(Ordering::Relaxed); + let deferred = abort_controller.raw_deferred.load(Ordering::Relaxed); + // abort function must be called from JavaScript main thread, so Relaxed Ordering is ok. + abort_controller.abort.store(true, Ordering::Relaxed); + napi_sys::napi_cancel_async_work(env, raw_async_work); + // napi_sys::napi_delete_async_work(env, raw_async_work); + let abort_error = Error::new(Status::Cancelled, "AbortError".to_owned()); + let reject_status = + napi_sys::napi_reject_deferred(env, deferred, JsError::from(abort_error).into_value(env)); + debug_assert_eq!( + reject_status, + napi_sys::Status::napi_ok, + "{}", + "Reject AbortError failed" + ); + let mut undefined = ptr::null_mut(); + napi_sys::napi_get_undefined(env, &mut undefined); + undefined + } +} + +impl ToNapiValue for AsyncTask { + unsafe fn to_napi_value( + env: napi_sys::napi_env, + val: Self, + ) -> crate::Result { + if let Some(abort_controller) = val.abort_controller { + let async_promise = async_work::run(env, val.inner, Some(abort_controller.abort.clone()))?; + abort_controller + .raw_work + .store(async_promise.napi_async_work, Ordering::Relaxed); + abort_controller + .raw_deferred + .store(async_promise.deferred, Ordering::Relaxed); + Ok(async_promise.promise_object().0.value) + } else { + let async_promise = async_work::run(env, val.inner, None)?; + Ok(async_promise.promise_object().0.value) + } + } +} + +unsafe extern "C" fn async_task_abort_controller_finalize( + _env: napi_sys::napi_env, + finalize_data: *mut c_void, + _finalize_hint: *mut c_void, +) { + Box::from_raw(finalize_data as *mut AsyncTaskAbortController); +} diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index 343d8645..d85375c9 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -485,7 +485,7 @@ impl Env { #[cfg(feature = "napi5")] pub fn create_function_from_closure(&self, name: &str, callback: F) -> Result where - F: 'static + Send + Sync + Fn(crate::CallContext<'_>) -> Result, + F: 'static + Fn(crate::CallContext<'_>) -> Result, R: NapiRaw, { use crate::CallContext; @@ -925,7 +925,7 @@ impl Env { /// Run [Task](./trait.Task.html) in libuv thread pool, return [AsyncWorkPromise](./struct.AsyncWorkPromise.html) pub fn spawn(&self, task: T) -> Result { - async_work::run(self, task) + async_work::run(self.0, task, None) } pub fn run_in_scope(&self, executor: F) -> Result @@ -1253,7 +1253,7 @@ unsafe extern "C" fn drop_buffer( mem::drop(Vec::from_raw_parts(finalize_data as *mut u8, length, cap)); } -unsafe extern "C" fn raw_finalize( +pub(crate) unsafe extern "C" fn raw_finalize( env: sys::napi_env, finalize_data: *mut c_void, finalize_hint: *mut c_void, diff --git a/crates/napi/src/js_values/arraybuffer.rs b/crates/napi/src/js_values/arraybuffer.rs index 467b47fc..71fa7c98 100644 --- a/crates/napi/src/js_values/arraybuffer.rs +++ b/crates/napi/src/js_values/arraybuffer.rs @@ -3,10 +3,21 @@ use std::os::raw::c_void; use std::ptr; use std::slice; +use crate::bindgen_runtime::TypeName; use crate::{check_status, sys, JsUnknown, NapiValue, Ref, Result, Value, ValueType}; pub struct JsArrayBuffer(pub(crate) Value); +impl TypeName for JsArrayBuffer { + fn type_name() -> &'static str { + "ArrayBuffer" + } + + fn value_type() -> ValueType { + ValueType::Object + } +} + pub struct JsArrayBufferValue { pub(crate) value: JsArrayBuffer, len: usize, @@ -15,6 +26,16 @@ pub struct JsArrayBufferValue { pub struct JsTypedArray(pub(crate) Value); +impl TypeName for JsTypedArray { + fn type_name() -> &'static str { + "TypedArray" + } + + fn value_type() -> ValueType { + ValueType::Object + } +} + pub struct JsTypedArrayValue { pub arraybuffer: JsArrayBuffer, data: *mut c_void, @@ -25,6 +46,16 @@ pub struct JsTypedArrayValue { pub struct JsDataView(pub(crate) Value); +impl TypeName for JsDataView { + fn type_name() -> &'static str { + "DataView" + } + + fn value_type() -> ValueType { + ValueType::Object + } +} + pub struct JsDataViewValue { pub arraybuffer: JsArrayBuffer, _data: *mut c_void, diff --git a/crates/napi/src/js_values/bigint.rs b/crates/napi/src/js_values/bigint.rs index 2ce3fdb6..dd295b4b 100644 --- a/crates/napi/src/js_values/bigint.rs +++ b/crates/napi/src/js_values/bigint.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::ptr; use super::*; -use crate::{check_status, sys, Result}; +use crate::{bindgen_runtime::TypeName, check_status, sys, Result}; #[derive(Clone, Copy)] pub struct JsBigint { @@ -10,6 +10,16 @@ pub struct JsBigint { pub word_count: usize, } +impl TypeName for JsBigint { + fn type_name() -> &'static str { + "BigInt" + } + + fn value_type() -> ValueType { + ValueType::Bigint + } +} + impl JsBigint { pub(crate) fn from_raw_unchecked( env: sys::napi_env, diff --git a/crates/napi/src/js_values/boolean.rs b/crates/napi/src/js_values/boolean.rs index 1b0a12bb..277d3dd5 100644 --- a/crates/napi/src/js_values/boolean.rs +++ b/crates/napi/src/js_values/boolean.rs @@ -1,12 +1,23 @@ use std::convert::TryFrom; use super::Value; -use crate::check_status; +use crate::bindgen_runtime::TypeName; +use crate::{check_status, ValueType}; use crate::{sys, Error, Result}; #[derive(Clone, Copy)] pub struct JsBoolean(pub(crate) Value); +impl TypeName for JsBoolean { + fn type_name() -> &'static str { + "bool" + } + + fn value_type() -> crate::ValueType { + ValueType::Boolean + } +} + impl JsBoolean { pub fn get_value(&self) -> Result { let mut result = false; diff --git a/crates/napi/src/js_values/buffer.rs b/crates/napi/src/js_values/buffer.rs index 82a97a9f..2ca4c92c 100644 --- a/crates/napi/src/js_values/buffer.rs +++ b/crates/napi/src/js_values/buffer.rs @@ -5,11 +5,22 @@ use std::ptr; use super::Value; #[cfg(feature = "serde-json")] use super::ValueType; +use crate::bindgen_runtime::TypeName; use crate::check_status; use crate::{sys, JsUnknown, NapiValue, Ref, Result}; pub struct JsBuffer(pub(crate) Value); +impl TypeName for JsBuffer { + fn type_name() -> &'static str { + "Buffer" + } + + fn value_type() -> ValueType { + ValueType::Object + } +} + pub struct JsBufferValue { pub(crate) value: JsBuffer, data: mem::ManuallyDrop>, diff --git a/crates/napi/src/js_values/date.rs b/crates/napi/src/js_values/date.rs index b88669fe..2b6946c8 100644 --- a/crates/napi/src/js_values/date.rs +++ b/crates/napi/src/js_values/date.rs @@ -1,8 +1,18 @@ use super::check_status; -use crate::{sys, Result, Value}; +use crate::{bindgen_runtime::TypeName, sys, Result, Value, ValueType}; pub struct JsDate(pub(crate) Value); +impl TypeName for JsDate { + fn type_name() -> &'static str { + "Date" + } + + fn value_type() -> crate::ValueType { + ValueType::Object + } +} + impl JsDate { pub fn value_of(&self) -> Result { let mut timestamp: f64 = 0.0; diff --git a/crates/napi/src/js_values/function.rs b/crates/napi/src/js_values/function.rs index 59117862..17db971d 100644 --- a/crates/napi/src/js_values/function.rs +++ b/crates/napi/src/js_values/function.rs @@ -1,11 +1,22 @@ use std::ptr; use super::Value; -use crate::check_status; +use crate::bindgen_runtime::TypeName; +use crate::{check_status, ValueType}; use crate::{sys, Env, Error, JsObject, JsUnknown, NapiRaw, NapiValue, Result, Status}; pub struct JsFunction(pub(crate) Value); +impl TypeName for JsFunction { + fn type_name() -> &'static str { + "Function" + } + + fn value_type() -> crate::ValueType { + ValueType::Function + } +} + /// See [Working with JavaScript Functions](https://nodejs.org/api/n-api.html#n_api_working_with_javascript_functions). /// /// Example: diff --git a/crates/napi/src/js_values/mod.rs b/crates/napi/src/js_values/mod.rs index 34259bf8..4ab96f17 100644 --- a/crates/napi/src/js_values/mod.rs +++ b/crates/napi/src/js_values/mod.rs @@ -2,7 +2,9 @@ use std::convert::TryFrom; use std::ffi::CString; use std::ptr; -use crate::{check_status, sys, type_of, Callback, Error, Result, Status, ValueType}; +use crate::{ + bindgen_runtime::TypeName, check_status, sys, type_of, Callback, Error, Result, Status, ValueType, +}; #[cfg(feature = "serde-json")] mod de; @@ -60,11 +62,41 @@ pub struct JsUnknown(pub(crate) Value); #[derive(Clone, Copy)] pub struct JsNull(pub(crate) Value); +impl TypeName for JsNull { + fn type_name() -> &'static str { + "null" + } + + fn value_type() -> ValueType { + ValueType::Null + } +} + #[derive(Clone, Copy)] pub struct JsSymbol(pub(crate) Value); +impl TypeName for JsSymbol { + fn type_name() -> &'static str { + "symbol" + } + + fn value_type() -> ValueType { + ValueType::Symbol + } +} + pub struct JsExternal(pub(crate) Value); +impl TypeName for JsExternal { + fn type_name() -> &'static str { + "external" + } + + fn value_type() -> ValueType { + ValueType::External + } +} + macro_rules! impl_napi_value_trait { ($js_value:ident, $value_type:ident) => { impl NapiValue for $js_value { diff --git a/crates/napi/src/js_values/number.rs b/crates/napi/src/js_values/number.rs index 1b5782b4..e85fab73 100644 --- a/crates/napi/src/js_values/number.rs +++ b/crates/napi/src/js_values/number.rs @@ -1,12 +1,23 @@ use std::convert::TryFrom; use super::Value; -use crate::check_status; +use crate::bindgen_runtime::TypeName; +use crate::{check_status, ValueType}; use crate::{sys, Error, Result}; #[derive(Clone, Copy)] pub struct JsNumber(pub(crate) Value); +impl TypeName for JsNumber { + fn type_name() -> &'static str { + "f64" + } + + fn value_type() -> crate::ValueType { + ValueType::Number + } +} + impl JsNumber { pub fn get_uint32(&self) -> Result { let mut result = 0; diff --git a/crates/napi/src/js_values/object.rs b/crates/napi/src/js_values/object.rs index 59fbab1a..e1727041 100644 --- a/crates/napi/src/js_values/object.rs +++ b/crates/napi/src/js_values/object.rs @@ -8,6 +8,7 @@ use std::ptr; #[cfg(feature = "napi5")] use super::check_status; use super::Value; +use crate::bindgen_runtime::TypeName; #[cfg(feature = "napi5")] use crate::sys; #[cfg(feature = "napi5")] @@ -16,9 +17,20 @@ use crate::Env; use crate::Error; #[cfg(feature = "napi5")] use crate::Result; +use crate::ValueType; pub struct JsObject(pub(crate) Value); +impl TypeName for JsObject { + fn type_name() -> &'static str { + "Object" + } + + fn value_type() -> crate::ValueType { + ValueType::Object + } +} + #[cfg(feature = "napi5")] pub struct FinalizeContext { pub env: Env, diff --git a/crates/napi/src/js_values/string/mod.rs b/crates/napi/src/js_values/string/mod.rs index 726c5b2c..8aa26538 100644 --- a/crates/napi/src/js_values/string/mod.rs +++ b/crates/napi/src/js_values/string/mod.rs @@ -1,6 +1,8 @@ use std::mem; use std::ptr; +use crate::bindgen_runtime::TypeName; +use crate::ValueType; use crate::{check_status, sys, Result, Value}; pub use latin1::JsStringLatin1; @@ -14,6 +16,16 @@ mod utf8; #[derive(Clone, Copy)] pub struct JsString(pub(crate) Value); +impl TypeName for JsString { + fn type_name() -> &'static str { + "String" + } + + fn value_type() -> crate::ValueType { + ValueType::String + } +} + impl JsString { pub fn utf8_len(&self) -> Result { let mut length = 0; diff --git a/crates/napi/src/js_values/undefined.rs b/crates/napi/src/js_values/undefined.rs index 53557ce5..ede22046 100644 --- a/crates/napi/src/js_values/undefined.rs +++ b/crates/napi/src/js_values/undefined.rs @@ -1,4 +1,16 @@ +use crate::{bindgen_runtime::TypeName, ValueType}; + use super::Value; #[derive(Clone, Copy)] pub struct JsUndefined(pub(crate) Value); + +impl TypeName for JsUndefined { + fn type_name() -> &'static str { + "undefined" + } + + fn value_type() -> crate::ValueType { + ValueType::Undefined + } +} diff --git a/crates/napi/src/task.rs b/crates/napi/src/task.rs index c7372940..c85c8002 100644 --- a/crates/napi/src/task.rs +++ b/crates/napi/src/task.rs @@ -1,8 +1,11 @@ -use crate::{js_values::NapiValue, Env, Error, Result}; +use crate::{ + bindgen_runtime::{ToNapiValue, TypeName}, + Env, Error, Result, +}; pub trait Task: Send + Sized { type Output: Send + Sized + 'static; - type JsValue: NapiValue; + type JsValue: ToNapiValue + TypeName; /// Compute logic in libuv thread fn compute(&mut self) -> Result; @@ -14,4 +17,7 @@ pub trait Task: Send + Sized { fn reject(self, _env: Env, err: Error) -> Result { Err(err) } + + // after resolve or reject + fn finally() {} } diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 75aac183..f3f295de 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -42,6 +42,8 @@ Generated by [AVA](https://avajs.dev). export function concatStr(mutS: string): string␊ export function concatUtf16(s: string): string␊ export function concatLatin1(s: string): string␊ + export function withoutAbortController(a: number, b: number): Promise␊ + export function withAbortController(a: number, b: number, ctrl: AbortController): Promise␊ export function getBuffer(): Buffer␊ export class Animal {␊ readonly kind: Kind␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index bf6d133e..ba59cedd 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index c9c01474..eb8d9ad1 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -30,6 +30,8 @@ import { returnEither, either3, either4, + withoutAbortController, + withAbortController, } from '../' test('number', (t) => { @@ -160,3 +162,19 @@ test('either4', (t) => { t.is(either4({ v: 1 }), 1) t.is(either4({ v: 'world' }), 'world'.length) }) + +test('async task without abort controller', async (t) => { + t.is(await withoutAbortController(1, 2), 3) +}) + +test('async task with abort controller', async (t) => { + const ctrl = new AbortController() + const promise = withAbortController(1, 2, ctrl) + try { + ctrl.abort() + await promise + t.fail('Should throw AbortError') + } catch (err: unknown) { + t.is((err as Error).message, 'AbortError') + } +}) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 54b6e118..a2b7d8bc 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -32,6 +32,8 @@ export function contains(source: string, target: string): boolean export function concatStr(mutS: string): string export function concatUtf16(s: string): string export function concatLatin1(s: string): string +export function withoutAbortController(a: number, b: number): Promise +export function withAbortController(a: number, b: number, ctrl: AbortController): Promise export function getBuffer(): Buffer export class Animal { readonly kind: Kind diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index b739a35f..b99f81f2 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -15,4 +15,5 @@ mod number; mod object; mod serde; mod string; +mod task; mod typed_array; diff --git a/examples/napi/src/task.rs b/examples/napi/src/task.rs new file mode 100644 index 00000000..c1b6bad0 --- /dev/null +++ b/examples/napi/src/task.rs @@ -0,0 +1,31 @@ +use std::thread::sleep; + +use napi::bindgen_prelude::*; +use napi::Task; + +struct DelaySum(u32, u32); + +#[napi] +impl Task for DelaySum { + type Output = u32; + type JsValue = u32; + + fn compute(&mut self) -> Result { + sleep(std::time::Duration::from_secs(1)); + Ok(self.0 + self.1) + } + + fn resolve(self, _env: napi::Env, output: Self::Output) -> Result { + Ok(output) + } +} + +#[napi] +fn without_abort_controller(a: u32, b: u32) -> AsyncTask { + AsyncTask::new(DelaySum(a, b)) +} + +#[napi] +fn with_abort_controller(a: u32, b: u32, ctrl: AsyncTaskAbortController) -> AsyncTask { + AsyncTask::with_abort_controller(DelaySum(a, b), ctrl) +}