diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml deleted file mode 100644 index d50bb354..00000000 --- a/.github/workflows/auto-merge.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Dependabot auto merge - -on: - check_suite: - types: - - completed - -jobs: - merge: - name: Merge - runs-on: ubuntu-latest - steps: - - name: Auto merge - uses: ridedott/merge-me-action@v2 - with: - GITHUB_LOGIN: 'dependabot*' - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 0c2300a0..c0fff9c8 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -300,10 +300,7 @@ impl NapiFn { let module_register_name = get_register_ident(&name_str); let intermediate_ident = get_intermediate_ident(&name_str); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); - let cb_name = Ident::new( - &format!("__register__fn__{}_callback__", name_str), - Span::call_site(), - ); + let cb_name = Ident::new(&format!("{}_js_function", name_str), Span::call_site()); quote! { #[allow(non_snake_case)] #[allow(clippy::all)] @@ -322,7 +319,7 @@ impl NapiFn { "Failed to register function `{}`", #name_str, )?; - + napi::bindgen_prelude::register_js_function(#js_name, env, #cb_name, Some(#intermediate_ident)); Ok(fn_ptr) } diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index cfe6f58b..ee381ebc 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -31,7 +31,19 @@ napi6 = ["napi5", "napi-sys/napi6"] napi7 = ["napi6", "napi-sys/napi7"] napi8 = ["napi7", "napi-sys/napi8"] serde-json = ["serde", "serde_json"] +tokio_fs = ["tokio/fs"] +tokio_full = ["tokio/full"] +tokio_io_std = ["tokio/io-std"] +tokio_io_util = ["tokio/io-util"] +tokio_macros = ["tokio/macros"] +tokio_net = ["tokio/net"] +tokio_process = ["tokio/process"] tokio_rt = ["tokio", "napi4"] +tokio_signal = ["tokio/signal"] +tokio_stats = ["tokio/stats"] +tokio_sync = ["tokio/sync"] +tokio_test_util = ["tokio/test-util"] +tokio_time = ["tokio/time"] [dependencies] ctor = "0.1" @@ -56,4 +68,4 @@ optional = true version = "1" [target.'cfg(windows)'.dependencies] -windows = {version = "0.29", features = ["Win32_System_WindowsProgramming", "Win32_System_LibraryLoader", "Win32_Foundation"]} +windows = {version = "0.30", features = ["Win32_System_WindowsProgramming", "Win32_System_LibraryLoader", "Win32_Foundation"]} diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index 847e6376..0723a7f9 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -6,7 +6,9 @@ use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; use lazy_static::lazy_static; -use crate::{check_status, check_status_or_throw, sys, JsError, Property, Result}; +use crate::{ + check_status, check_status_or_throw, sys, JsError, JsFunction, Property, Result, Value, ValueType, +}; pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result; pub type ModuleExportsCallback = @@ -82,12 +84,18 @@ type ModuleClassProperty = PersistedSingleThreadHashMap< HashMap, (&'static str, Vec)>, >; +type FnRegisterMap = PersistedSingleThreadHashMap< + ExportRegisterCallback, + (sys::napi_env, sys::napi_callback, &'static str), +>; + unsafe impl Send for PersistedSingleThreadHashMap {} unsafe impl Sync for PersistedSingleThreadHashMap {} lazy_static! { static ref MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default(); static ref MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default(); + static ref FN_REGISTER_MAP: FnRegisterMap = Default::default(); } #[cfg(feature = "compat-mode")] @@ -103,6 +111,7 @@ thread_local! { >> = Default::default(); } +#[doc(hidden)] pub fn get_class_constructor(js_name: &'static str) -> Option { REGISTERED_CLASSES.with(|registered_classes| { let classes = registered_classes.borrow(); @@ -110,12 +119,14 @@ pub fn get_class_constructor(js_name: &'static str) -> Option { }) } +#[doc(hidden)] #[cfg(feature = "compat-mode")] // compatibility for #[module_exports] pub fn register_module_exports(callback: ModuleExportsCallback) { MODULE_EXPORTS.push(callback); } +#[doc(hidden)] pub fn register_module_export( js_mod: Option<&'static str>, name: &'static str, @@ -124,6 +135,17 @@ pub fn register_module_export( MODULE_REGISTER_CALLBACK.push((js_mod, (name, cb))); } +#[doc(hidden)] +pub fn register_js_function( + name: &'static str, + env: sys::napi_env, + cb: ExportRegisterCallback, + c_fn: sys::napi_callback, +) { + FN_REGISTER_MAP.borrow_mut().insert(cb, (env, c_fn, name)); +} + +#[doc(hidden)] pub fn register_class( rust_name: &'static str, js_mod: Option<&'static str>, @@ -138,6 +160,57 @@ pub fn register_class( val.1.extend(props.into_iter()); } +#[inline] +/// Get `JsFunction` from defined Rust `fn` +/// ```rust +/// #[napi] +/// fn some_fn() -> u32 { +/// 1 +/// } +/// +/// #[napi] +/// fn return_some_fn() -> Result { +/// get_js_function(some_fn_js_function) +/// } +/// ``` +/// +/// ```js +/// returnSomeFn()(); // 1 +/// ``` +/// +pub fn get_js_function(raw_fn: ExportRegisterCallback) -> Result { + FN_REGISTER_MAP + .borrow_mut() + .get(&raw_fn) + .and_then(|(env, cb, name)| { + let mut function = ptr::null_mut(); + let name_len = name.len() - 1; + let fn_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) }; + check_status!(unsafe { + sys::napi_create_function( + *env, + fn_name.as_ptr(), + name_len, + *cb, + ptr::null_mut(), + &mut function, + ) + }) + .ok()?; + Some(JsFunction(Value { + env: *env, + value: function, + value_type: ValueType::Function, + })) + }) + .ok_or_else(|| { + crate::Error::new( + crate::Status::InvalidArg, + "JavaScript function does not exists".to_owned(), + ) + }) +} + #[no_mangle] unsafe extern "C" fn napi_register_module_v1( env: sys::napi_env, @@ -191,17 +264,13 @@ unsafe extern "C" fn napi_register_module_v1( let js_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) }; unsafe { if let Err(e) = callback(env).and_then(|v| { + let exported_object = if exports_js_mod.is_null() { + exports + } else { + exports_js_mod + }; check_status!( - sys::napi_set_named_property( - env, - if exports_js_mod.is_null() { - exports - } else { - exports_js_mod - }, - js_name.as_ptr(), - v - ), + sys::napi_set_named_property(env, exported_object, js_name.as_ptr(), v), "Failed to register export `{}`", name, ) diff --git a/crates/napi/src/lib.rs b/crates/napi/src/lib.rs index df45d69f..69b30e99 100644 --- a/crates/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -23,12 +23,10 @@ //! use napi::{CallContext, Error, JsObject, JsString, Result, Status}; //! use tokio; //! -//! #[js_function(1)] -//! pub fn tokio_readfile(ctx: CallContext) -> Result { -//! let js_filepath = ctx.get::(0)?; -//! let path_str = js_filepath.as_str()?; +//! #[napi] +//! pub async fn tokio_readfile(js_filepath: String) -> Result { //! ctx.env.execute_tokio_future( -//! tokio::fs::read(path_str.to_owned()) +//! tokio::fs::read(js_filepath) //! .map(|v| v.map_err(|e| Error::new(Status::Unknown, format!("failed to read file, {}", e)))), //! |&mut env, data| env.create_buffer_with_data(data), //! ) @@ -61,17 +59,16 @@ //! c: String, //! } //! -//! #[js_function(1)] -//! fn deserialize_from_js(ctx: CallContext) -> Result { -//! let arg0 = ctx.get::(0)?; +//! #[napi] +//! fn deserialize_from_js(arg0: JsUnknown) -> Result { //! let de_serialized: AnObject = ctx.env.from_js_value(arg0)?; //! ... //! } //! -//! #[js_function] -//! fn serialize(ctx: CallContext) -> Result { +//! #[napi] +//! fn serialize(env: Env) -> Result { //! let value = AnyObject { a: 1, b: vec![0.1, 2.22], c: "hello" }; -//! ctx.env.to_js_value(&value) +//! env.to_js_value(&value) //! } //! ``` //! @@ -201,3 +198,6 @@ pub mod bindgen_prelude { type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType, }; } + +#[cfg(feature = "tokio_rt")] +pub extern crate tokio; diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 2fc11d9f..9ccaf160 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -10,12 +10,11 @@ crate-type = ["cdylib"] [dependencies] futures = "0.3" -napi = {path = "../../crates/napi", default-features = false, features = ["napi8", "tokio_rt", "serde-json", "async", "experimental", "latin1"]} +napi = {path = "../../crates/napi", default-features = false, features = ["tokio_fs", "napi8", "tokio_rt", "serde-json", "async", "experimental", "latin1"]} napi-derive = {path = "../../crates/macro", features = ["type-def"]} serde = "1" serde_derive = "1" serde_json = "1" -tokio = {version = "1", features = ["default", "fs"]} [build-dependencies] napi-build = {path = "../../crates/build"} diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 2eff2439..96a2b1a6 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -38,6 +38,7 @@ Generated by [AVA](https://avajs.dev). export function optionOnly(callback: (arg0?: string | undefined | null) => void): void␊ /** napi = { version = 2, features = ["serde-json"] } */␊ export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊ + export function returnJsFunction(): (...args: any[]) => any␊ export function eitherStringOrNumber(input: string | number): number␊ export function returnEither(input: number): string | number␊ export function either3(input: string | number | boolean): number␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index d015efb7..81724e3c 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 4dd4d185..e1fc69f4 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -77,6 +77,7 @@ import { JsClassForEither, receiveMutClassOrNumber, getStrFromObject, + returnJsFunction, } from '../' test('export const', (t) => { @@ -193,6 +194,16 @@ test('callback', (t) => { }) }) +test('return function', (t) => { + return new Promise((resolve) => { + returnJsFunction()((err: Error | undefined, content: string) => { + t.is(err, undefined) + t.is(content, 'hello world') + resolve() + }) + }) +}) + test('object', (t) => { t.deepEqual(listObjKeys({ name: 'John Doe', age: 20 }), ['name', 'age']) t.deepEqual(createObj(), { test: 1 }) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 4dad7295..54b7eac8 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -28,6 +28,7 @@ export function optionStartEnd(callback: (arg0: string | undefined | null, arg1: export function optionOnly(callback: (arg0?: string | undefined | null) => void): void /** napi = { version = 2, features = ["serde-json"] } */ export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void +export function returnJsFunction(): (...args: any[]) => any export function eitherStringOrNumber(input: string | number): number export function returnEither(input: number): string | number export function either3(input: string | number | boolean): number diff --git a/examples/napi/src/async.rs b/examples/napi/src/async.rs index 76ea50bd..5220ecfc 100644 --- a/examples/napi/src/async.rs +++ b/examples/napi/src/async.rs @@ -1,6 +1,6 @@ use futures::prelude::*; use napi::bindgen_prelude::*; -use tokio::fs; +use napi::tokio::{self, fs}; #[napi] async fn read_file_async(path: String) -> Result { diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs index 4254faca..eaa1da9b 100644 --- a/examples/napi/src/callback.rs +++ b/examples/napi/src/callback.rs @@ -1,6 +1,7 @@ -use napi::bindgen_prelude::*; use std::env; +use napi::bindgen_prelude::*; + #[napi] fn get_cwd Result<()>>(callback: T) { callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); @@ -40,3 +41,8 @@ fn read_file_content() -> Result { // serde_json::from_str(&s)?; Ok("hello world".to_string()) } + +#[napi] +fn return_js_function() -> Result { + get_js_function(read_file_js_function) +}