Merge pull request #1048 from napi-rs/feat/get-js-function
Feat/get js function
This commit is contained in:
commit
135b27acf4
12 changed files with 128 additions and 49 deletions
17
.github/workflows/auto-merge.yaml
vendored
17
.github/workflows/auto-merge.yaml
vendored
|
@ -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 }}
|
|
|
@ -300,10 +300,7 @@ impl NapiFn {
|
||||||
let module_register_name = get_register_ident(&name_str);
|
let module_register_name = get_register_ident(&name_str);
|
||||||
let intermediate_ident = get_intermediate_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 js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
|
||||||
let cb_name = Ident::new(
|
let cb_name = Ident::new(&format!("{}_js_function", name_str), Span::call_site());
|
||||||
&format!("__register__fn__{}_callback__", name_str),
|
|
||||||
Span::call_site(),
|
|
||||||
);
|
|
||||||
quote! {
|
quote! {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(clippy::all)]
|
#[allow(clippy::all)]
|
||||||
|
@ -322,7 +319,7 @@ impl NapiFn {
|
||||||
"Failed to register function `{}`",
|
"Failed to register function `{}`",
|
||||||
#name_str,
|
#name_str,
|
||||||
)?;
|
)?;
|
||||||
|
napi::bindgen_prelude::register_js_function(#js_name, env, #cb_name, Some(#intermediate_ident));
|
||||||
Ok(fn_ptr)
|
Ok(fn_ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,19 @@ napi6 = ["napi5", "napi-sys/napi6"]
|
||||||
napi7 = ["napi6", "napi-sys/napi7"]
|
napi7 = ["napi6", "napi-sys/napi7"]
|
||||||
napi8 = ["napi7", "napi-sys/napi8"]
|
napi8 = ["napi7", "napi-sys/napi8"]
|
||||||
serde-json = ["serde", "serde_json"]
|
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_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]
|
[dependencies]
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
|
@ -56,4 +68,4 @@ optional = true
|
||||||
version = "1"
|
version = "1"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[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"]}
|
||||||
|
|
|
@ -6,7 +6,9 @@ use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
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<sys::napi_value>;
|
pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_value>;
|
||||||
pub type ModuleExportsCallback =
|
pub type ModuleExportsCallback =
|
||||||
|
@ -82,12 +84,18 @@ type ModuleClassProperty = PersistedSingleThreadHashMap<
|
||||||
HashMap<Option<&'static str>, (&'static str, Vec<Property>)>,
|
HashMap<Option<&'static str>, (&'static str, Vec<Property>)>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type FnRegisterMap = PersistedSingleThreadHashMap<
|
||||||
|
ExportRegisterCallback,
|
||||||
|
(sys::napi_env, sys::napi_callback, &'static str),
|
||||||
|
>;
|
||||||
|
|
||||||
unsafe impl<K, V> Send for PersistedSingleThreadHashMap<K, V> {}
|
unsafe impl<K, V> Send for PersistedSingleThreadHashMap<K, V> {}
|
||||||
unsafe impl<K, V> Sync for PersistedSingleThreadHashMap<K, V> {}
|
unsafe impl<K, V> Sync for PersistedSingleThreadHashMap<K, V> {}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default();
|
static ref MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default();
|
||||||
static ref MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default();
|
static ref MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default();
|
||||||
|
static ref FN_REGISTER_MAP: FnRegisterMap = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "compat-mode")]
|
#[cfg(feature = "compat-mode")]
|
||||||
|
@ -103,6 +111,7 @@ thread_local! {
|
||||||
>> = Default::default();
|
>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
|
pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
|
||||||
REGISTERED_CLASSES.with(|registered_classes| {
|
REGISTERED_CLASSES.with(|registered_classes| {
|
||||||
let classes = registered_classes.borrow();
|
let classes = registered_classes.borrow();
|
||||||
|
@ -110,12 +119,14 @@ pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[cfg(feature = "compat-mode")]
|
#[cfg(feature = "compat-mode")]
|
||||||
// compatibility for #[module_exports]
|
// compatibility for #[module_exports]
|
||||||
pub fn register_module_exports(callback: ModuleExportsCallback) {
|
pub fn register_module_exports(callback: ModuleExportsCallback) {
|
||||||
MODULE_EXPORTS.push(callback);
|
MODULE_EXPORTS.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn register_module_export(
|
pub fn register_module_export(
|
||||||
js_mod: Option<&'static str>,
|
js_mod: Option<&'static str>,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
@ -124,6 +135,17 @@ pub fn register_module_export(
|
||||||
MODULE_REGISTER_CALLBACK.push((js_mod, (name, cb)));
|
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(
|
pub fn register_class(
|
||||||
rust_name: &'static str,
|
rust_name: &'static str,
|
||||||
js_mod: Option<&'static str>,
|
js_mod: Option<&'static str>,
|
||||||
|
@ -138,6 +160,57 @@ pub fn register_class(
|
||||||
val.1.extend(props.into_iter());
|
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<JsFunction> {
|
||||||
|
/// get_js_function(some_fn_js_function)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```js
|
||||||
|
/// returnSomeFn()(); // 1
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn get_js_function(raw_fn: ExportRegisterCallback) -> Result<JsFunction> {
|
||||||
|
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]
|
#[no_mangle]
|
||||||
unsafe extern "C" fn napi_register_module_v1(
|
unsafe extern "C" fn napi_register_module_v1(
|
||||||
env: sys::napi_env,
|
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()) };
|
let js_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) };
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Err(e) = callback(env).and_then(|v| {
|
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!(
|
check_status!(
|
||||||
sys::napi_set_named_property(
|
sys::napi_set_named_property(env, exported_object, js_name.as_ptr(), v),
|
||||||
env,
|
|
||||||
if exports_js_mod.is_null() {
|
|
||||||
exports
|
|
||||||
} else {
|
|
||||||
exports_js_mod
|
|
||||||
},
|
|
||||||
js_name.as_ptr(),
|
|
||||||
v
|
|
||||||
),
|
|
||||||
"Failed to register export `{}`",
|
"Failed to register export `{}`",
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,12 +23,10 @@
|
||||||
//! use napi::{CallContext, Error, JsObject, JsString, Result, Status};
|
//! use napi::{CallContext, Error, JsObject, JsString, Result, Status};
|
||||||
//! use tokio;
|
//! use tokio;
|
||||||
//!
|
//!
|
||||||
//! #[js_function(1)]
|
//! #[napi]
|
||||||
//! pub fn tokio_readfile(ctx: CallContext) -> Result<JsObject> {
|
//! pub async fn tokio_readfile(js_filepath: String) -> Result<JsBuffer> {
|
||||||
//! let js_filepath = ctx.get::<JsString>(0)?;
|
|
||||||
//! let path_str = js_filepath.as_str()?;
|
|
||||||
//! ctx.env.execute_tokio_future(
|
//! 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)))),
|
//! .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),
|
//! |&mut env, data| env.create_buffer_with_data(data),
|
||||||
//! )
|
//! )
|
||||||
|
@ -61,17 +59,16 @@
|
||||||
//! c: String,
|
//! c: String,
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[js_function(1)]
|
//! #[napi]
|
||||||
//! fn deserialize_from_js(ctx: CallContext) -> Result<JsUndefined> {
|
//! fn deserialize_from_js(arg0: JsUnknown) -> Result<JsUndefined> {
|
||||||
//! let arg0 = ctx.get::<JsUnknown>(0)?;
|
|
||||||
//! let de_serialized: AnObject = ctx.env.from_js_value(arg0)?;
|
//! let de_serialized: AnObject = ctx.env.from_js_value(arg0)?;
|
||||||
//! ...
|
//! ...
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[js_function]
|
//! #[napi]
|
||||||
//! fn serialize(ctx: CallContext) -> Result<JsUnknown> {
|
//! fn serialize(env: Env) -> Result<JsUnknown> {
|
||||||
//! let value = AnyObject { a: 1, b: vec![0.1, 2.22], c: "hello" };
|
//! 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,
|
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tokio_rt")]
|
||||||
|
pub extern crate tokio;
|
||||||
|
|
|
@ -10,12 +10,11 @@ crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3"
|
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"]}
|
napi-derive = {path = "../../crates/macro", features = ["type-def"]}
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = {version = "1", features = ["default", "fs"]}
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = {path = "../../crates/build"}
|
napi-build = {path = "../../crates/build"}
|
||||||
|
|
|
@ -38,6 +38,7 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export function optionOnly(callback: (arg0?: string | undefined | null) => void): void␊
|
export function optionOnly(callback: (arg0?: string | undefined | null) => void): void␊
|
||||||
/** napi = { version = 2, features = ["serde-json"] } */␊
|
/** napi = { version = 2, features = ["serde-json"] } */␊
|
||||||
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊
|
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 eitherStringOrNumber(input: string | number): number␊
|
||||||
export function returnEither(input: number): string | number␊
|
export function returnEither(input: number): string | number␊
|
||||||
export function either3(input: string | number | boolean): number␊
|
export function either3(input: string | number | boolean): number␊
|
||||||
|
|
Binary file not shown.
|
@ -77,6 +77,7 @@ import {
|
||||||
JsClassForEither,
|
JsClassForEither,
|
||||||
receiveMutClassOrNumber,
|
receiveMutClassOrNumber,
|
||||||
getStrFromObject,
|
getStrFromObject,
|
||||||
|
returnJsFunction,
|
||||||
} from '../'
|
} from '../'
|
||||||
|
|
||||||
test('export const', (t) => {
|
test('export const', (t) => {
|
||||||
|
@ -193,6 +194,16 @@ test('callback', (t) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('return function', (t) => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
returnJsFunction()((err: Error | undefined, content: string) => {
|
||||||
|
t.is(err, undefined)
|
||||||
|
t.is(content, 'hello world')
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('object', (t) => {
|
test('object', (t) => {
|
||||||
t.deepEqual(listObjKeys({ name: 'John Doe', age: 20 }), ['name', 'age'])
|
t.deepEqual(listObjKeys({ name: 'John Doe', age: 20 }), ['name', 'age'])
|
||||||
t.deepEqual(createObj(), { test: 1 })
|
t.deepEqual(createObj(), { test: 1 })
|
||||||
|
|
1
examples/napi/index.d.ts
vendored
1
examples/napi/index.d.ts
vendored
|
@ -28,6 +28,7 @@ export function optionStartEnd(callback: (arg0: string | undefined | null, arg1:
|
||||||
export function optionOnly(callback: (arg0?: string | undefined | null) => void): void
|
export function optionOnly(callback: (arg0?: string | undefined | null) => void): void
|
||||||
/** napi = { version = 2, features = ["serde-json"] } */
|
/** napi = { version = 2, features = ["serde-json"] } */
|
||||||
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void
|
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 eitherStringOrNumber(input: string | number): number
|
||||||
export function returnEither(input: number): string | number
|
export function returnEither(input: number): string | number
|
||||||
export function either3(input: string | number | boolean): number
|
export function either3(input: string | number | boolean): number
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
use tokio::fs;
|
use napi::tokio::{self, fs};
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
async fn read_file_async(path: String) -> Result<Buffer> {
|
async fn read_file_async(path: String) -> Result<Buffer> {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use napi::bindgen_prelude::*;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use napi::bindgen_prelude::*;
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
|
fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
|
||||||
callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
|
callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
|
||||||
|
@ -40,3 +41,8 @@ fn read_file_content() -> Result<String> {
|
||||||
// serde_json::from_str(&s)?;
|
// serde_json::from_str(&s)?;
|
||||||
Ok("hello world".to_string())
|
Ok("hello world".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
fn return_js_function() -> Result<JsFunction> {
|
||||||
|
get_js_function(read_file_js_function)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue