Merge pull request #852 from napi-rs/global
feat(napi): support JsGlobal in `Env`
This commit is contained in:
commit
c0a89834c2
15 changed files with 148 additions and 100 deletions
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
|
@ -61,7 +61,7 @@ jobs:
|
|||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy
|
||||
run: cargo clippy --all-features
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
|
|
|
@ -242,7 +242,7 @@ yarn test
|
|||
| T: Fn(...) -> Result<T> | Function | 1 | v8.0.0 |
|
||||
| Async/Future | Promise<T> | 4 | v10.6.0 | async |
|
||||
| AsyncTask | Promise<T> | 1 | v8.5.0 |
|
||||
| (NOT YET) | global | 1 | v8.0.0 |
|
||||
| JsGlobal | global | 1 | v8.0.0 |
|
||||
| JsSymbol | Symbol | 1 | v8.0.0 |
|
||||
| (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 |
|
||||
| JsFunction | threadsafe function | 4 | v10.6.0 | napi4 |
|
||||
|
|
|
@ -75,6 +75,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
|||
("external", "object"),
|
||||
("AbortSignal", "AbortSignal"),
|
||||
("JsFunction", "(...args: any[]) => any"),
|
||||
("JsGlobal", "typeof global"),
|
||||
]);
|
||||
|
||||
map
|
||||
|
|
|
@ -1,22 +1,57 @@
|
|||
use crate::{sys, Result};
|
||||
use std::cell::RefCell;
|
||||
use std::ptr;
|
||||
|
||||
use super::{Array, Object};
|
||||
use crate::{check_status, sys, JsGlobal, JsNull, JsUndefined, NapiValue, Result};
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Env(sys::napi_env);
|
||||
use super::Array;
|
||||
|
||||
impl From<sys::napi_env> for Env {
|
||||
fn from(raw_env: sys::napi_env) -> Env {
|
||||
Env(raw_env)
|
||||
}
|
||||
pub use crate::Env;
|
||||
|
||||
thread_local! {
|
||||
static JS_UNDEFINED: RefCell<Option<JsUndefined>> = RefCell::default();
|
||||
static JS_NULL: RefCell<Option<JsNull>> = RefCell::default();
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn create_object(&self) -> Result<Object> {
|
||||
Object::new(self.0)
|
||||
}
|
||||
|
||||
pub fn create_array(&self, len: u32) -> Result<Array> {
|
||||
Array::new(self.0, len)
|
||||
}
|
||||
|
||||
/// Get [JsUndefined](./struct.JsUndefined.html) value
|
||||
pub fn get_undefined(&self) -> Result<JsUndefined> {
|
||||
if let Some(js_undefined) = JS_UNDEFINED.with(|x| *x.borrow()) {
|
||||
return Ok(js_undefined);
|
||||
}
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?;
|
||||
let js_undefined = unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) };
|
||||
JS_UNDEFINED.with(|x| x.borrow_mut().replace(js_undefined));
|
||||
Ok(js_undefined)
|
||||
}
|
||||
|
||||
pub fn get_null(&self) -> Result<JsNull> {
|
||||
if let Some(js_null) = JS_NULL.with(|cell| *cell.borrow()) {
|
||||
return Ok(js_null);
|
||||
}
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?;
|
||||
let js_null = unsafe { JsNull::from_raw_unchecked(self.0, raw_value) };
|
||||
JS_NULL.with(|js_null_cell| {
|
||||
js_null_cell.borrow_mut().replace(js_null);
|
||||
});
|
||||
Ok(js_null)
|
||||
}
|
||||
|
||||
pub fn get_global(&self) -> Result<JsGlobal> {
|
||||
let mut global = std::ptr::null_mut();
|
||||
crate::check_status!(
|
||||
unsafe { sys::napi_get_global(self.0, &mut global) },
|
||||
"Get global object from Env failed"
|
||||
)?;
|
||||
Ok(JsGlobal(crate::Value {
|
||||
value: global,
|
||||
env: self.0,
|
||||
value_type: crate::ValueType::Object,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use crate::{bindgen_prelude::*, check_status, sys, type_of, ValueType};
|
||||
use crate::{bindgen_prelude::*, check_status, sys, type_of, JsObject, ValueType};
|
||||
use std::{ffi::CString, ptr};
|
||||
|
||||
pub struct Object {
|
||||
pub(crate) env: sys::napi_env,
|
||||
pub(crate) inner: sys::napi_value,
|
||||
}
|
||||
pub type Object = JsObject;
|
||||
|
||||
impl Object {
|
||||
pub(crate) fn new(env: sys::napi_env) -> Result<Self> {
|
||||
|
@ -16,7 +13,11 @@ impl Object {
|
|||
)?;
|
||||
}
|
||||
|
||||
Ok(Object { env, inner: ptr })
|
||||
Ok(Self(crate::Value {
|
||||
env,
|
||||
value: ptr,
|
||||
value_type: ValueType::Object,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn get<K: AsRef<str>, V: FromNapiValue>(&self, field: K) -> Result<Option<V>> {
|
||||
|
@ -26,17 +27,17 @@ impl Object {
|
|||
let mut ret = ptr::null_mut();
|
||||
|
||||
check_status!(
|
||||
sys::napi_get_named_property(self.env, self.inner, c_field.as_ptr(), &mut ret),
|
||||
sys::napi_get_named_property(self.0.env, self.0.value, c_field.as_ptr(), &mut ret),
|
||||
"Failed to get property with field `{}`",
|
||||
c_field.to_string_lossy(),
|
||||
)?;
|
||||
|
||||
let ty = type_of!(self.env, ret)?;
|
||||
let ty = type_of!(self.0.env, ret)?;
|
||||
|
||||
Ok(if ty == ValueType::Undefined {
|
||||
None
|
||||
} else {
|
||||
Some(V::from_napi_value(self.env, ret)?)
|
||||
Some(V::from_napi_value(self.0.env, ret)?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -45,10 +46,10 @@ impl Object {
|
|||
let c_field = CString::new(field.as_ref())?;
|
||||
|
||||
unsafe {
|
||||
let napi_val = V::to_napi_value(self.env, val)?;
|
||||
let napi_val = V::to_napi_value(self.0.env, val)?;
|
||||
|
||||
check_status!(
|
||||
sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val),
|
||||
sys::napi_set_named_property(self.0.env, self.0.value, c_field.as_ptr(), napi_val),
|
||||
"Failed to set property with field `{}`",
|
||||
c_field.to_string_lossy(),
|
||||
)?;
|
||||
|
@ -61,12 +62,12 @@ impl Object {
|
|||
let mut names = ptr::null_mut();
|
||||
unsafe {
|
||||
check_status!(
|
||||
sys::napi_get_property_names(obj.env, obj.inner, &mut names),
|
||||
sys::napi_get_property_names(obj.0.env, obj.0.value, &mut names),
|
||||
"Failed to get property names of given object"
|
||||
)?;
|
||||
}
|
||||
|
||||
let names = unsafe { Array::from_napi_value(obj.env, names)? };
|
||||
let names = unsafe { Array::from_napi_value(obj.0.env, names)? };
|
||||
let mut ret = vec![];
|
||||
|
||||
for i in 0..names.len() {
|
||||
|
@ -86,25 +87,3 @@ impl TypeName for Object {
|
|||
ValueType::Object
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNapiValue for Object {
|
||||
unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||
Ok(val.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNapiValue for Object {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
let value_type = type_of!(env, napi_val)?;
|
||||
match value_type {
|
||||
ValueType::Object => Ok(Self {
|
||||
inner: napi_val,
|
||||
env,
|
||||
}),
|
||||
_ => Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
"Given napi value is not an object".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::{bindgen_runtime::Null, check_status, sys, type_of, Error, Result, Status, ValueType};
|
||||
use crate::{
|
||||
bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType,
|
||||
};
|
||||
|
||||
use super::{FromNapiValue, Object, ToNapiValue};
|
||||
|
||||
|
@ -78,10 +80,11 @@ impl ToNapiValue for Map<String, Value> {
|
|||
|
||||
impl FromNapiValue for Map<String, Value> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
let obj = Object {
|
||||
let obj = JsObject(crate::Value {
|
||||
env,
|
||||
inner: napi_val,
|
||||
};
|
||||
value: napi_val,
|
||||
value_type: ValueType::Object,
|
||||
});
|
||||
|
||||
let mut map = Map::new();
|
||||
for key in Object::keys(&obj)?.into_iter() {
|
||||
|
|
|
@ -43,25 +43,18 @@ pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys
|
|||
/// Notification of this event is delivered through the callbacks given to `Env::add_env_cleanup_hook` and `Env::set_instance_data`.
|
||||
pub struct Env(pub(crate) sys::napi_env);
|
||||
|
||||
impl From<sys::napi_env> for Env {
|
||||
fn from(env: sys::napi_env) -> Self {
|
||||
Env(env)
|
||||
}
|
||||
}
|
||||
|
||||
impl Env {
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub unsafe fn from_raw(env: sys::napi_env) -> Self {
|
||||
Env(env)
|
||||
}
|
||||
|
||||
/// Get [JsUndefined](./struct.JsUndefined.html) value
|
||||
pub fn get_undefined(&self) -> Result<JsUndefined> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?;
|
||||
Ok(unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
pub fn get_null(&self) -> Result<JsNull> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?;
|
||||
Ok(unsafe { JsNull::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) })?;
|
||||
|
@ -231,7 +224,7 @@ impl Env {
|
|||
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) })
|
||||
}
|
||||
|
||||
pub fn create_array(&self) -> Result<JsObject> {
|
||||
pub fn create_empty_array(&self) -> Result<JsObject> {
|
||||
let mut raw_value = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_create_array(self.0, &mut raw_value) })?;
|
||||
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) })
|
||||
|
@ -949,12 +942,6 @@ impl Env {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn get_global(&self) -> Result<JsGlobal> {
|
||||
let mut raw_global = ptr::null_mut();
|
||||
check_status!(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?;
|
||||
Ok(unsafe { JsGlobal::from_raw_unchecked(self.0, raw_global) })
|
||||
}
|
||||
|
||||
pub fn get_napi_version(&self) -> Result<u32> {
|
||||
let global = self.get_global()?;
|
||||
let process: JsObject = global.get_named_property("process")?;
|
||||
|
|
|
@ -9,7 +9,7 @@ pub struct JsTimeout(pub(crate) Value);
|
|||
|
||||
impl JsGlobal {
|
||||
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
|
||||
let func: JsFunction = self.get_named_property("setInterval")?;
|
||||
let func: JsFunction = self.get_named_property_unchecked("setInterval")?;
|
||||
func
|
||||
.call(
|
||||
None,
|
||||
|
@ -24,14 +24,14 @@ impl JsGlobal {
|
|||
}
|
||||
|
||||
pub fn clear_interval(&self, timer: JsTimeout) -> Result<JsUndefined> {
|
||||
let func: JsFunction = self.get_named_property("clearInterval")?;
|
||||
let func: JsFunction = self.get_named_property_unchecked("clearInterval")?;
|
||||
func
|
||||
.call(None, &[timer.into_unknown()])
|
||||
.and_then(|ret| ret.try_into())
|
||||
}
|
||||
|
||||
pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
|
||||
let func: JsFunction = self.get_named_property("setTimeout")?;
|
||||
let func: JsFunction = self.get_named_property_unchecked("setTimeout")?;
|
||||
func
|
||||
.call(
|
||||
None,
|
||||
|
@ -46,7 +46,7 @@ impl JsGlobal {
|
|||
}
|
||||
|
||||
pub fn clear_timeout(&self, timer: JsTimeout) -> Result<JsUndefined> {
|
||||
let func: JsFunction = self.get_named_property("clearTimeout")?;
|
||||
let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?;
|
||||
func
|
||||
.call(None, &[timer.into_unknown()])
|
||||
.and_then(|ret| ret.try_into())
|
||||
|
|
|
@ -8,7 +8,6 @@ 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")]
|
||||
|
@ -17,20 +16,9 @@ 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<T: 'static, Hint: 'static> {
|
||||
pub env: Env,
|
||||
|
|
|
@ -7,7 +7,7 @@ use napi::{
|
|||
|
||||
#[contextless_function]
|
||||
fn test_create_array(env: Env) -> ContextlessResult<JsObject> {
|
||||
env.create_array().map(Some)
|
||||
env.create_empty_array().map(Some)
|
||||
}
|
||||
|
||||
#[js_function(1)]
|
||||
|
|
|
@ -34,6 +34,9 @@ Generated by [AVA](https://avajs.dev).
|
|||
export function fibonacci(n: number): number␊
|
||||
export function listObjKeys(obj: object): Array<string>␊
|
||||
export function createObj(): object␊
|
||||
export function getGlobal(): typeof global␊
|
||||
export function getUndefined(): JsUndefined␊
|
||||
export function getNull(): JsNull␊
|
||||
export function asyncPlus100(p: Promise<number>): Promise<number>␊
|
||||
interface PackageJson {␊
|
||||
name: string␊
|
||||
|
|
Binary file not shown.
|
@ -41,6 +41,9 @@ import {
|
|||
callThreadsafeFunction,
|
||||
threadsafeFunctionThrowError,
|
||||
asyncPlus100,
|
||||
getGlobal,
|
||||
getUndefined,
|
||||
getNull,
|
||||
} from '../'
|
||||
|
||||
test('number', (t) => {
|
||||
|
@ -137,6 +140,22 @@ test('object', (t) => {
|
|||
t.deepEqual(createObj(), { test: 1 })
|
||||
})
|
||||
|
||||
test('global', (t) => {
|
||||
t.is(getGlobal(), global)
|
||||
})
|
||||
|
||||
test('get undefined', (t) => {
|
||||
for (const _ of Array.from({ length: 100 })) {
|
||||
t.is(getUndefined(), undefined)
|
||||
}
|
||||
})
|
||||
|
||||
test('get null', (t) => {
|
||||
for (const _ of Array.from({ length: 100 })) {
|
||||
t.is(getNull(), null)
|
||||
}
|
||||
})
|
||||
|
||||
test('Option', (t) => {
|
||||
t.is(mapOption(null), null)
|
||||
t.is(mapOption(3), 4)
|
||||
|
|
36
examples/napi/index.d.ts
vendored
36
examples/napi/index.d.ts
vendored
|
@ -7,7 +7,9 @@ export function bigintAdd(a: BigInt, b: BigInt): BigInt
|
|||
export function createBigInt(): BigInt
|
||||
export function createBigIntI64(): BigInt
|
||||
export function getCwd(callback: (arg0: string) => void): void
|
||||
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void
|
||||
export function readFile(
|
||||
callback: (arg0: Error | undefined, arg1: string | null) => void,
|
||||
): void
|
||||
export function eitherStringOrNumber(input: string | number): number
|
||||
export function returnEither(input: number): string | number
|
||||
export function either3(input: string | number | boolean): number
|
||||
|
@ -15,8 +17,21 @@ interface Obj {
|
|||
v: string | number
|
||||
}
|
||||
export function either4(input: string | number | boolean | Obj): number
|
||||
export enum Kind { Dog = 0, Cat = 1, Duck = 2 }
|
||||
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
|
||||
export enum Kind {
|
||||
Dog = 0,
|
||||
Cat = 1,
|
||||
Duck = 2,
|
||||
}
|
||||
export enum CustomNumEnum {
|
||||
One = 1,
|
||||
Two = 2,
|
||||
Three = 3,
|
||||
Four = 4,
|
||||
Six = 6,
|
||||
Eight = 8,
|
||||
Nine = 9,
|
||||
Ten = 10,
|
||||
}
|
||||
export function enumToI32(e: CustomNumEnum): number
|
||||
export function throwError(): void
|
||||
export function mapOption(val: number | null): number | null
|
||||
|
@ -24,6 +39,9 @@ export function add(a: number, b: number): number
|
|||
export function fibonacci(n: number): number
|
||||
export function listObjKeys(obj: object): Array<string>
|
||||
export function createObj(): object
|
||||
export function getGlobal(): typeof global
|
||||
export function getUndefined(): JsUndefined
|
||||
export function getNull(): JsNull
|
||||
export function asyncPlus100(p: Promise<number>): Promise<number>
|
||||
interface PackageJson {
|
||||
name: string
|
||||
|
@ -38,7 +56,11 @@ export function concatStr(s: string): string
|
|||
export function concatUtf16(s: string): string
|
||||
export function concatLatin1(s: string): string
|
||||
export function withoutAbortController(a: number, b: number): Promise<number>
|
||||
export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number>
|
||||
export function withAbortController(
|
||||
a: number,
|
||||
b: number,
|
||||
signal: AbortSignal,
|
||||
): Promise<number>
|
||||
export function callThreadsafeFunction(callback: (...args: any[]) => any): void
|
||||
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void
|
||||
export function getBuffer(): Buffer
|
||||
|
@ -52,14 +74,10 @@ export class Animal {
|
|||
static getDogKind(): Kind
|
||||
}
|
||||
export class Blake2BHasher {
|
||||
|
||||
static withKey(key: Blake2bKey): Blake2BHasher
|
||||
}
|
||||
export class Blake2BKey {
|
||||
|
||||
}
|
||||
export class Blake2BKey {}
|
||||
export class Context {
|
||||
|
||||
constructor()
|
||||
static withData(data: string): Context
|
||||
method(): string
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use napi::bindgen_prelude::*;
|
||||
use napi::{bindgen_prelude::*, JsGlobal, JsNull, JsUndefined};
|
||||
|
||||
#[napi]
|
||||
fn list_obj_keys(obj: Object) -> Vec<String> {
|
||||
|
@ -12,3 +12,18 @@ fn create_obj(env: Env) -> Object {
|
|||
|
||||
obj
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn get_global(env: Env) -> Result<JsGlobal> {
|
||||
env.get_global()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn get_undefined(env: Env) -> Result<JsUndefined> {
|
||||
env.get_undefined()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn get_null(env: Env) -> Result<JsNull> {
|
||||
env.get_null()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue