feat(napi): impl ReadableStream and AsyncGenerator (#2418)

* feat(napi): impl ReadableStream and AsyncGenerator

* clippy fix

* Into<Vec<u8>> trait

* Skip node18 stream test due to Node.js bug

* Cleanup

* Also skip wasi test

* Merge test

* Skip wasi

* Useless expect-error
This commit is contained in:
LongYinan 2025-01-03 17:09:42 +08:00 committed by GitHub
parent aad71cdb00
commit 98cb7671d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 1573 additions and 262 deletions

View File

@ -17,11 +17,11 @@ impl Task for BufferLength {
}
#[js_function(1)]
fn bench_async_task(ctx: CallContext) -> Result<PromiseRaw<u32>> {
fn bench_async_task(ctx: CallContext) -> Result<Unknown> {
let n = ctx.get::<Buffer>(0)?;
let task = BufferLength(n);
let async_promise = ctx.env.spawn(task)?;
Ok(async_promise.promise_object())
Ok(async_promise.promise_object().into_unknown())
}
#[js_function(2)]

View File

@ -1,7 +1,7 @@
import { build} from 'esbuild'
import { pull } from 'lodash-es'
import packageJson from './package.json' assert { type: 'json' }
import packageJson from './package.json' with { type: 'json' }
await build({
entryPoints: ['./dist/index.js'],

View File

@ -418,7 +418,7 @@ impl NapiStruct {
return quote! {};
}
quote! {
napi::__private::create_iterator::<#name>(env, instance_value, wrapped_value);
unsafe { napi::__private::create_iterator::<#name>(env, instance_value, wrapped_value); }
}
}

View File

@ -190,6 +190,7 @@ static KNOWN_TYPES: LazyLock<HashMap<&'static str, (&'static str, bool, bool)>>
("ClassInstance", ("{}", false, false)),
("Function", ("({}) => {}", true, false)),
("FunctionRef", ("({}) => {}", true, false)),
("ReadableStream", ("ReadableStream<{}>", false, false)),
("Either", ("{} | {}", false, true)),
("Either3", ("{} | {} | {}", false, true)),
("Either4", ("{} | {} | {} | {}", false, true)),
@ -507,7 +508,7 @@ pub fn ty_to_ts_type(
});
// Generic type handling
if args.len() > 0 {
if !args.is_empty() {
let arg_str = args
.iter()
.map(|(arg, _)| arg.clone())
@ -515,10 +516,7 @@ pub fn ty_to_ts_type(
.join(", ");
let mut ty = rust_ty;
if let Some((alias, _)) = type_alias {
ty = alias
.split_once('<')
.and_then(|(t, _)| Some(t.to_string()))
.unwrap();
ty = alias.split_once('<').map(|(t, _)| t.to_string()).unwrap();
}
Some((format!("{}<{}>", ty, arg_str), false))

View File

@ -1464,14 +1464,14 @@ impl ConvertToAST for syn::ItemType {
let js_name = match opts.js_name() {
Some((name, _)) => name.to_string(),
_ => {
if self.generics.params.len() > 0 {
if !self.generics.params.is_empty() {
let types = self
.generics
.type_params()
.map(|param| param.ident.to_string())
.collect::<Vec<String>>()
.join(", ");
format!("{}<{}>", self.ident.to_string(), types)
format!("{}<{}>", self.ident, types)
} else {
self.ident.to_string()
}

View File

@ -40,6 +40,7 @@ napi6 = ["napi5", "napi-sys/napi6"]
napi7 = ["napi6", "napi-sys/napi7"]
napi8 = ["napi7", "napi-sys/napi8"]
napi9 = ["napi8", "napi-sys/napi9"]
web_stream = ["futures-core", "tokio-stream", "napi4", "tokio_rt"]
noop = []
serde-json = ["serde", "serde_json"]
serde-json-ordered = ["serde-json", "serde_json/preserve_order"]
@ -101,5 +102,13 @@ version = "1"
optional = true
version = "2"
[dependencies.futures-core]
optional = true
version = "0.3"
[dependencies.tokio-stream]
optional = true
version = "0.1"
[build-dependencies]
napi-build = { path = "../build", version = "2.1.3" }

View File

@ -0,0 +1,434 @@
use std::future::Future;
use std::ptr;
use crate::{
bindgen_runtime::{FromNapiValue, Object, ToNapiValue, Unknown},
check_status, check_status_or_throw, sys, Env, JsError, Value,
};
/// Implement a Iterator for the JavaScript Class.
/// This feature is an experimental feature and is not yet stable.
pub trait AsyncGenerator {
type Yield: ToNapiValue + Send + 'static;
type Next: FromNapiValue;
type Return: FromNapiValue;
/// Handle the `AsyncGenerator.next()`
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/next>
fn next(
&mut self,
value: Option<Self::Next>,
) -> impl Future<Output = crate::Result<Option<Self::Yield>>> + Send + 'static;
#[allow(unused_variables)]
/// Implement complete to handle the `AsyncGenerator.return()`
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/return>
fn complete(
&mut self,
value: Option<Self::Return>,
) -> impl Future<Output = crate::Result<Option<Self::Yield>>> + Send + 'static {
async move { Ok(None) }
}
#[allow(unused_variables)]
/// Implement catch to handle the `AsyncGenerator.throw()`
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/throw>
fn catch(
&mut self,
env: Env,
value: Unknown,
) -> impl Future<Output = crate::Result<Option<Self::Yield>>> + Send + 'static {
let err = value.into();
async move { Err(err) }
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn create_async_iterator<T: AsyncGenerator>(
env: sys::napi_env,
instance: sys::napi_value,
generator_ptr: *mut T,
) {
let mut global = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_global(env, &mut global) },
"Get global object failed",
);
let mut symbol_object = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_named_property(env, global, c"Symbol".as_ptr().cast(), &mut symbol_object)
},
"Get global object failed",
);
let mut iterator_symbol = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_named_property(
env,
symbol_object,
c"asyncIterator".as_ptr().cast(),
&mut iterator_symbol,
)
},
"Get Symbol.asyncIterator failed",
);
let mut generator_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
c"AsyncIterator".as_ptr().cast(),
8,
Some(symbol_async_generator::<T>),
generator_ptr.cast(),
&mut generator_function,
)
},
"Create asyncIterator function failed",
);
check_status_or_throw!(
env,
unsafe { sys::napi_set_property(env, instance, iterator_symbol, generator_function) },
"Failed to set Symbol.asyncIterator on class instance",
);
}
#[doc(hidden)]
pub unsafe extern "C" fn symbol_async_generator<T: AsyncGenerator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 0;
let mut generator_ptr = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
);
let mut generator_object = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_create_object(env, &mut generator_object) },
"Create Generator object failed"
);
let mut next_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
c"next".as_ptr().cast(),
4,
Some(generator_next::<T>),
generator_ptr,
&mut next_function,
)
},
"Create next function failed"
);
let mut return_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
c"return".as_ptr().cast(),
6,
Some(generator_return::<T>),
generator_ptr,
&mut return_function,
)
},
"Create next function failed"
);
let mut throw_function = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_create_function(
env,
c"throw".as_ptr().cast(),
5,
Some(generator_throw::<T>),
generator_ptr,
&mut throw_function,
)
},
"Create next function failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
generator_object,
c"next".as_ptr().cast(),
next_function,
)
},
"Set next function on Generator object failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
generator_object,
c"return".as_ptr().cast(),
return_function,
)
},
"Set return function on Generator object failed"
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
generator_object,
c"throw".as_ptr().cast(),
throw_function,
)
},
"Set throw function on Generator object failed"
);
let mut generator_state = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_boolean(env, false, &mut generator_state) },
"Create generator state failed"
);
generator_object
}
extern "C" fn generator_next<T: AsyncGenerator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
match generator_next_fn::<T>(env, info) {
Ok(value) => value,
Err(e) => unsafe {
let js_error: JsError = e.into();
js_error.throw_into(env);
ptr::null_mut()
},
}
}
fn generator_next_fn<T: AsyncGenerator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> crate::Result<sys::napi_value> {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 1;
let mut generator_ptr = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
)?;
let g = unsafe { Box::leak(Box::from_raw(generator_ptr as *mut T)) };
let item = if argc == 0 {
g.next(None)
} else {
g.next(match unsafe { T::Next::from_napi_value(env, argv[0]) } {
Ok(input) => Some(input),
Err(e) => {
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr().cast(),
e.reason.as_ptr().cast(),
)
};
None
}
})
};
let env = Env::from_raw(env);
let promise: crate::bindgen_runtime::PromiseRaw<'_, Option<T::Yield>> = env
.spawn_future_with_callback(item, |env, value| {
if let Some(v) = value {
let mut obj = Object::new(env.0)?;
obj.set("value", v)?;
obj.set("done", false)?;
Ok(obj)
} else {
let mut obj = Object::new(env.0)?;
obj.set("value", ())?;
obj.set("done", true)?;
Ok(obj)
}
})?;
Ok(promise.inner)
}
extern "C" fn generator_return<T: AsyncGenerator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 1;
let mut generator_ptr = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
);
let g = unsafe { Box::leak(Box::from_raw(generator_ptr as *mut T)) };
match Env::from_raw(env).spawn_future_with_callback(
g.complete(if argc == 0 {
None
} else {
Some(match unsafe { T::Return::from_napi_value(env, argv[0]) } {
Ok(input) => input,
Err(e) => {
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr().cast(),
e.reason.as_ptr().cast(),
)
};
return ptr::null_mut();
}
})
}),
|env, value| {
let mut obj = Object::new(env.0)?;
if let Some(v) = value {
obj.set("value", v)?;
obj.set("done", false)?;
Ok(obj)
} else {
obj.set("value", ())?;
obj.set("done", true)?;
Ok(obj)
}
},
) {
Ok(promise) => promise.inner,
Err(e) => {
unsafe {
sys::napi_throw_error(
env,
e.status.as_ref().as_ptr().cast(),
e.reason.as_ptr().cast(),
);
}
ptr::null_mut()
}
}
}
extern "C" fn generator_throw<T: AsyncGenerator>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
let mut this = ptr::null_mut();
let mut argv: [sys::napi_value; 1] = [ptr::null_mut()];
let mut argc = 1;
let mut generator_ptr = ptr::null_mut();
check_status_or_throw!(
env,
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
argv.as_mut_ptr(),
&mut this,
&mut generator_ptr,
)
},
"Get callback info from generator function failed"
);
let g = unsafe { Box::leak(Box::from_raw(generator_ptr as *mut T)) };
let caught = if argc == 0 {
let mut undefined = ptr::null_mut();
check_status_or_throw!(
env,
unsafe { sys::napi_get_undefined(env, &mut undefined) },
"Get undefined failed"
);
g.catch(
Env(env),
Unknown(Value {
env,
value: undefined,
value_type: crate::ValueType::Undefined,
}),
)
} else {
g.catch(
Env(env),
Unknown(Value {
env,
value: argv[0],
value_type: crate::ValueType::Unknown,
}),
)
};
match Env::from_raw(env).spawn_future_with_callback(caught, |env, value| {
let mut obj = Object::new(env.0)?;
obj.set("value", value)?;
obj.set("done", false)?;
Ok(obj)
}) {
Ok(promise) => promise.inner,
Err(e) => {
unsafe {
sys::napi_throw_error(
env,
e.status.as_ref().as_ptr().cast(),
e.reason.as_ptr().cast(),
);
}
ptr::null_mut()
}
}
}

View File

@ -146,7 +146,7 @@ impl<const N: usize> CallbackInfo<N> {
obj: T,
) -> Result<sys::napi_value> {
let (instance, generator_ptr) = self._construct::<IsEmptyStructHint, T>(js_name, obj)?;
crate::__private::create_iterator(self.env, instance, generator_ptr);
unsafe { crate::__private::create_iterator(self.env, instance, generator_ptr) };
Ok(instance)
}
@ -164,7 +164,7 @@ impl<const N: usize> CallbackInfo<N> {
obj: T,
) -> Result<sys::napi_value> {
let (instance, generator_ptr) = self._factory(js_name, obj)?;
crate::__private::create_iterator(self.env, instance, generator_ptr);
unsafe { crate::__private::create_iterator(self.env, instance, generator_ptr) };
Ok(instance)
}

View File

@ -1,5 +1,5 @@
use std::ffi::c_void;
use std::ptr;
use std::{ffi::c_void, os::raw::c_char};
use crate::Value;
use crate::{bindgen_runtime::Unknown, check_status_or_throw, sys, Env};
@ -34,8 +34,9 @@ pub trait Generator {
}
}
#[doc(hidden)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn create_iterator<T: Generator>(
pub unsafe fn create_iterator<T: Generator>(
env: sys::napi_env,
instance: sys::napi_value,
generator_ptr: *mut T,
@ -288,8 +289,8 @@ extern "C" fn generator_next<T: Generator>(
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr() as *mut c_char,
e.reason.as_ptr() as *mut c_char,
format!("{}", e.status).as_ptr().cast(),
e.reason.as_ptr().cast(),
)
};
None
@ -311,14 +312,7 @@ extern "C" fn generator_next<T: Generator>(
);
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
c"done".as_ptr() as *const std::os::raw::c_char,
completed_value,
)
},
unsafe { sys::napi_set_named_property(env, result, c"done".as_ptr().cast(), completed_value,) },
"Failed to set iterator result done",
);
@ -359,8 +353,8 @@ extern "C" fn generator_return<T: Generator>(
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr() as *mut c_char,
e.reason.as_ptr() as *mut c_char,
format!("{}", e.status).as_ptr().cast(),
e.reason.as_ptr().cast(),
)
};
return ptr::null_mut();
@ -395,14 +389,7 @@ extern "C" fn generator_return<T: Generator>(
if argc > 0 {
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
c"value".as_ptr() as *const std::os::raw::c_char,
argv[0],
)
},
unsafe { sys::napi_set_named_property(env, result, c"value".as_ptr().cast(), argv[0],) },
"Failed to set iterator result value",
);
}
@ -545,14 +532,7 @@ fn set_generator_value<V: ToNapiValue>(env: sys::napi_env, result: sys::napi_val
Ok(val) => {
check_status_or_throw!(
env,
unsafe {
sys::napi_set_named_property(
env,
result,
c"value".as_ptr() as *const std::os::raw::c_char,
val,
)
},
unsafe { sys::napi_set_named_property(env, result, c"value".as_ptr().cast(), val,) },
"Failed to set iterator result value",
);
}
@ -560,8 +540,8 @@ fn set_generator_value<V: ToNapiValue>(env: sys::napi_env, result: sys::napi_val
unsafe {
sys::napi_throw_error(
env,
format!("{}", e.status).as_ptr() as *mut c_char,
e.reason.as_ptr() as *mut c_char,
format!("{}", e.status).as_ptr().cast(),
e.reason.as_ptr().cast(),
)
};
}

View File

@ -28,6 +28,8 @@ mod promise_raw;
#[cfg(feature = "serde-json")]
mod serde;
mod set;
#[cfg(feature = "web_stream")]
mod stream;
mod string;
mod symbol;
mod task;
@ -50,6 +52,8 @@ pub use object::*;
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
pub use promise::*;
pub use promise_raw::*;
#[cfg(feature = "web_stream")]
pub use stream::*;
pub use string::*;
pub use symbol::*;
pub use task::*;

View File

@ -103,7 +103,7 @@ impl<'scope> BufferSlice<'scope> {
///
/// If you need to support these runtimes, you should create a buffer by other means and then
/// later copy the data back out.
pub unsafe fn from_external<T: 'scope, F: FnOnce(T, Env)>(
pub unsafe fn from_external<T: 'scope, F: FnOnce(Env, T)>(
env: &Env,
data: *mut u8,
len: usize,
@ -142,7 +142,7 @@ impl<'scope> BufferSlice<'scope> {
let (hint, finalize) = *Box::from_raw(hint_ptr);
let status =
unsafe { sys::napi_create_buffer_copy(env.0, len, data.cast(), ptr::null_mut(), &mut buf) };
finalize(hint, *env);
finalize(*env, hint);
status
} else {
status

View File

@ -260,6 +260,39 @@ impl<Args: JsValuesTupleIntoVec, Return: FromNapiValue> Function<'_, Args, Retur
)?;
unsafe { Return::from_napi_value(self.env, raw_return) }
}
/// Call `Function.bind`
pub fn bind<T: ToNapiValue>(&self, this: T) -> Result<Function<'_, Args, Return>> {
let raw_this = unsafe { T::to_napi_value(self.env, this) }?;
let mut bind_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(self.env, self.value, c"bind".as_ptr(), &mut bind_function)
},
"Get bind function failed"
)?;
let mut bound_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
self.value,
bind_function,
1,
[raw_this].as_ptr(),
&mut bound_function,
)
},
"Bind function failed"
)?;
Ok(Function {
env: self.env,
value: bound_function,
_args: std::marker::PhantomData,
_return: std::marker::PhantomData,
_scope: std::marker::PhantomData,
})
}
}
#[cfg(feature = "napi4")]

View File

@ -59,7 +59,7 @@ unsafe impl<T: FromNapiValue + Send> Send for Promise<T> {}
impl<T: FromNapiValue> FromNapiValue for Promise<T> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
let (tx, rx) = channel();
let mut promise_object = unsafe { PromiseRaw::<T>::from_napi_value(env, napi_val)? };
let promise_object = unsafe { PromiseRaw::<T>::from_napi_value(env, napi_val)? };
let tx_box = Arc::new(Cell::new(Some(tx)));
let tx_in_catch = tx_box.clone();
promise_object

View File

@ -10,25 +10,31 @@ use crate::{
};
use crate::{Env, Error, NapiRaw, NapiValue, Status};
pub struct PromiseRaw<T> {
use super::Unknown;
pub struct PromiseRaw<'env, T> {
pub(crate) inner: sys::napi_value,
env: sys::napi_env,
_phantom: PhantomData<T>,
_phantom: &'env PhantomData<T>,
}
impl<T> PromiseRaw<T> {
impl<T> PromiseRaw<'_, T> {
pub(crate) fn new(env: sys::napi_env, inner: sys::napi_value) -> Self {
Self {
inner,
env,
_phantom: PhantomData,
_phantom: &PhantomData,
}
}
pub fn into_unknown(self) -> Unknown {
unsafe { Unknown::from_raw_unchecked(self.env, self.inner) }
}
}
impl<T: FromNapiValue> PromiseRaw<T> {
impl<'env, T: FromNapiValue> PromiseRaw<'env, T> {
/// Promise.then method
pub fn then<Callback, U>(&mut self, cb: Callback) -> Result<PromiseRaw<U>>
pub fn then<Callback, U>(&self, cb: Callback) -> Result<PromiseRaw<'env, U>>
where
U: ToNapiValue,
Callback: 'static + FnOnce(CallbackContext<T>) -> Result<U>,
@ -88,12 +94,12 @@ impl<T: FromNapiValue> PromiseRaw<T> {
Ok(PromiseRaw::<U> {
env: self.env,
inner: new_promise,
_phantom: PhantomData,
_phantom: &PhantomData,
})
}
/// Promise.catch method
pub fn catch<E, U, Callback>(&mut self, cb: Callback) -> Result<PromiseRaw<U>>
pub fn catch<E, U, Callback>(&self, cb: Callback) -> Result<PromiseRaw<'env, U>>
where
E: FromNapiValue,
U: ToNapiValue,
@ -154,12 +160,12 @@ impl<T: FromNapiValue> PromiseRaw<T> {
Ok(PromiseRaw::<U> {
env: self.env,
inner: new_promise,
_phantom: PhantomData,
_phantom: &PhantomData,
})
}
/// Promise.finally method
pub fn finally<U, Callback>(&mut self, cb: Callback) -> Result<PromiseRaw<T>>
pub fn finally<U, Callback>(&mut self, cb: Callback) -> Result<PromiseRaw<'env, T>>
where
U: ToNapiValue,
Callback: 'static + FnOnce(Env) -> Result<U>,
@ -203,7 +209,7 @@ impl<T: FromNapiValue> PromiseRaw<T> {
Ok(Self {
env: self.env,
inner: new_promise,
_phantom: PhantomData,
_phantom: &PhantomData,
})
}
@ -216,7 +222,7 @@ impl<T: FromNapiValue> PromiseRaw<T> {
}
}
impl<T: FromNapiValue> TypeName for PromiseRaw<T> {
impl<T: FromNapiValue> TypeName for PromiseRaw<'_, T> {
fn type_name() -> &'static str {
"Promise"
}
@ -226,7 +232,7 @@ impl<T: FromNapiValue> TypeName for PromiseRaw<T> {
}
}
impl<T: FromNapiValue> ValidateNapiValue for PromiseRaw<T> {
impl<T: FromNapiValue> ValidateNapiValue for PromiseRaw<'_, T> {
unsafe fn validate(
env: napi_sys::napi_env,
napi_val: napi_sys::napi_value,
@ -235,13 +241,13 @@ impl<T: FromNapiValue> ValidateNapiValue for PromiseRaw<T> {
}
}
impl<T> NapiRaw for PromiseRaw<T> {
impl<T> NapiRaw for PromiseRaw<'_, T> {
unsafe fn raw(&self) -> sys::napi_value {
self.inner
}
}
impl<T> NapiValue for PromiseRaw<T> {
impl<T> NapiValue for PromiseRaw<'_, T> {
unsafe fn from_raw(env: napi_sys::napi_env, value: napi_sys::napi_value) -> Result<Self> {
let mut is_promise = false;
check_status!(unsafe { sys::napi_is_promise(env, value, &mut is_promise) })?;
@ -249,7 +255,7 @@ impl<T> NapiValue for PromiseRaw<T> {
.then_some(Self {
env,
inner: value,
_phantom: PhantomData,
_phantom: &PhantomData,
})
.ok_or_else(|| Error::new(Status::InvalidArg, "JavaScript value is not Promise"))
}
@ -258,7 +264,7 @@ impl<T> NapiValue for PromiseRaw<T> {
Self {
env,
inner: value,
_phantom: PhantomData,
_phantom: &PhantomData,
}
}
}

View File

@ -0,0 +1,5 @@
pub use read::*;
pub use write::*;
mod read;
mod write;

View File

@ -0,0 +1,545 @@
use std::{
marker::PhantomData,
mem,
pin::Pin,
ptr,
sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
},
task::{Context, Poll},
};
use futures_core::Stream;
use tokio_stream::StreamExt;
use crate::{
bindgen_prelude::{
CallbackContext, FromNapiValue, Function, PromiseRaw, ToNapiValue, TypeName, Unknown,
ValidateNapiValue,
},
bindgen_runtime::{BufferSlice, Null, Object, NAPI_AUTO_LENGTH},
check_status, sys,
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
Env, Error, JsError, NapiRaw, Result, Status, ValueType,
};
pub struct ReadableStream<'env, T> {
pub(crate) value: sys::napi_value,
pub(crate) env: sys::napi_env,
_marker: PhantomData<&'env T>,
}
impl<T> NapiRaw for ReadableStream<'_, T> {
unsafe fn raw(&self) -> sys::napi_value {
self.value
}
}
impl<T> TypeName for ReadableStream<'_, T> {
fn type_name() -> &'static str {
"ReadableStream"
}
fn value_type() -> ValueType {
ValueType::Object
}
}
impl<T> ValidateNapiValue for ReadableStream<'_, T> {
unsafe fn validate(
env: napi_sys::napi_env,
napi_val: napi_sys::napi_value,
) -> Result<napi_sys::napi_value> {
let constructor = Env::from(env)
.get_global()?
.get_named_property_unchecked::<Function>("ReadableStream")?;
let mut is_instance = false;
check_status!(
unsafe { sys::napi_instanceof(env, napi_val, constructor.value, &mut is_instance) },
"Check ReadableStream instance failed"
)?;
if !is_instance {
return Err(Error::new(
Status::InvalidArg,
"Value is not a ReadableStream",
));
}
Ok(ptr::null_mut())
}
}
impl<T> FromNapiValue for ReadableStream<'_, T> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
Ok(Self {
value: napi_val,
env,
_marker: PhantomData,
})
}
}
impl<T> ReadableStream<'_, T> {
/// Returns a boolean indicating whether or not the readable stream is locked to a reader.
pub fn locked(&self) -> Result<bool> {
let mut locked = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(self.env, self.value, c"locked".as_ptr().cast(), &mut locked)
},
"Get locked property failed"
)?;
unsafe { FromNapiValue::from_napi_value(self.env, locked) }
}
/// The `cancel()` method of the `ReadableStream` interface returns a Promise that resolves when the stream is canceled.
pub fn cancel(&mut self, reason: Option<String>) -> Result<PromiseRaw<()>> {
let mut cancel_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
self.value,
c"abort".as_ptr().cast(),
&mut cancel_fn,
)
},
"Get abort property failed"
)?;
let reason_value = unsafe { ToNapiValue::to_napi_value(self.env, reason)? };
let mut promise = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
self.value,
cancel_fn,
1,
[reason_value].as_ptr(),
&mut promise,
)
},
"Call abort function failed"
)?;
Ok(PromiseRaw::new(self.env, promise))
}
}
impl<T: FromNapiValue> ReadableStream<'_, T> {
pub fn read(&self) -> Result<Reader<T>> {
let mut reader_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
self.value,
c"getReader".as_ptr().cast(),
&mut reader_function,
)
},
"Get getReader on ReadableStream failed"
)?;
let mut reader = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
self.value,
reader_function,
0,
ptr::null_mut(),
&mut reader,
)
},
"Call getReader on ReadableStreamReader failed"
)?;
let mut read_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
reader,
c"read".as_ptr().cast(),
&mut read_function,
)
},
"Get read from ReadableStreamDefaultReader failed"
)?;
let mut bind_function = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
read_function,
c"bind".as_ptr().cast(),
&mut bind_function,
)
},
"Get bind from ReadableStreamDefaultReader::read failed"
)?;
let mut bind_read = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
read_function,
bind_function,
1,
[reader].as_ptr(),
&mut bind_read,
)
},
"Call bind from ReadableStreamDefaultReader::read failed"
)?;
let read_function = unsafe {
Function::<(), PromiseRaw<IteratorValue<T>>>::from_napi_value(self.env, bind_read)?
}
.build_threadsafe_function()
.callee_handled::<true>()
.weak::<true>()
.build()?;
Ok(Reader {
inner: read_function,
state: Arc::new((RwLock::new(Ok(None)), AtomicBool::new(false))),
})
}
}
impl<T: ToNapiValue + Send + 'static> ReadableStream<'_, T> {
pub fn new<S: Stream<Item = Result<T>> + Unpin + Send + 'static>(
env: &Env,
inner: S,
) -> Result<Self> {
let global = env.get_global()?;
let constructor = global.get_named_property_unchecked::<Function>("ReadableStream")?;
let mut underlying_source = Object::new(env.raw())?;
let mut pull_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"pull".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(pull_callback::<T, S>),
Box::into_raw(Box::new(inner)).cast(),
&mut pull_fn,
)
},
"Failed to create pull function"
)?;
underlying_source.set_named_property("pull", pull_fn)?;
let mut stream = ptr::null_mut();
check_status!(
unsafe {
sys::napi_new_instance(
env.0,
constructor.value,
1,
[underlying_source.0.value].as_ptr(),
&mut stream,
)
},
"Create ReadableStream instance failed"
)?;
Ok(Self {
value: stream,
env: env.0,
_marker: PhantomData,
})
}
}
impl<'env> ReadableStream<'env, BufferSlice<'env>> {
pub fn create_with_stream_bytes<
B: Into<Vec<u8>>,
S: Stream<Item = Result<B>> + Unpin + Send + 'static,
>(
env: &Env,
inner: S,
) -> Result<Self> {
let global = env.get_global()?;
let constructor = global.get_named_property_unchecked::<Function>("ReadableStream")?;
let mut underlying_source = Object::new(env.raw())?;
let mut pull_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_create_function(
env.raw(),
c"pull".as_ptr().cast(),
NAPI_AUTO_LENGTH,
Some(pull_callback_bytes::<B, S>),
Box::into_raw(Box::new(inner)).cast(),
&mut pull_fn,
)
},
"Failed to create pull function"
)?;
underlying_source.set_named_property("pull", pull_fn)?;
underlying_source.set("type", "bytes")?;
let mut stream = ptr::null_mut();
check_status!(
unsafe {
sys::napi_new_instance(
env.0,
constructor.value,
1,
[underlying_source.0.value].as_ptr(),
&mut stream,
)
},
"Create ReadableStream instance failed"
)?;
Ok(Self {
value: stream,
env: env.0,
_marker: PhantomData,
})
}
}
pub struct IteratorValue<'env, T: FromNapiValue> {
_marker: PhantomData<&'env ()>,
value: Option<T>,
done: bool,
}
impl<T: FromNapiValue> FromNapiValue for IteratorValue<'_, T> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let mut done = ptr::null_mut();
check_status!(
unsafe { sys::napi_get_named_property(env, napi_val, c"done".as_ptr().cast(), &mut done) },
"Get done property failed"
)?;
let done = unsafe { FromNapiValue::from_napi_value(env, done)? };
let mut value = ptr::null_mut();
check_status!(
unsafe { sys::napi_get_named_property(env, napi_val, c"value".as_ptr().cast(), &mut value) },
"Get value property failed"
)?;
let value = unsafe { FromNapiValue::from_napi_value(env, value)? };
Ok(Self {
value,
done,
_marker: PhantomData,
})
}
}
pub struct Reader<T: FromNapiValue + 'static> {
inner: ThreadsafeFunction<(), PromiseRaw<'static, IteratorValue<'static, T>>, (), true, true>,
state: Arc<(RwLock<Result<Option<T>>>, AtomicBool)>,
}
impl<T: FromNapiValue + 'static> futures_core::Stream for Reader<T> {
type Item = Result<T>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.state.1.load(Ordering::Relaxed) {
let mut chunk = self
.state
.0
.write()
.map_err(|_| Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next"))?;
let chunk = mem::replace(&mut *chunk, Ok(None))?;
match chunk {
Some(chunk) => return Poll::Ready(Some(Ok(chunk))),
None => return Poll::Ready(None),
}
}
let waker = cx.waker().clone();
let state = self.state.clone();
let state_in_catch = state.clone();
self.inner.call_with_return_value(
Ok(()),
ThreadsafeFunctionCallMode::NonBlocking,
move |iterator, _| {
let iterator = iterator?;
iterator
.then(move |cx| {
if cx.value.done {
state.1.store(true, Ordering::Relaxed);
}
if let Some(val) = cx.value.value {
let mut chunk = state.0.write().map_err(|_| {
Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next")
})?;
*chunk = Ok(Some(val));
};
Ok(())
})?
.catch(move |cx: CallbackContext<Unknown>| {
let mut chunk = state_in_catch
.0
.write()
.map_err(|_| Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next"))?;
let mut error_ref = ptr::null_mut();
check_status!(
unsafe { sys::napi_create_reference(cx.env.0, cx.value.0.value, 0, &mut error_ref) },
"Create error reference failed"
)?;
*chunk = Err(Error {
status: Status::GenericFailure,
reason: "".to_string(),
maybe_raw: error_ref,
maybe_env: cx.env.0,
raw: true,
});
Ok(())
})?
.finally(move |_| {
waker.wake();
Ok(())
})?;
Ok(())
},
);
let mut chunk = self
.state
.0
.write()
.map_err(|_| Error::new(Status::InvalidArg, "Poisoned lock in Reader::poll_next"))?;
let chunk = mem::replace(&mut *chunk, Ok(None))?;
match chunk {
Some(chunk) => Poll::Ready(Some(Ok(chunk))),
None => Poll::Pending,
}
}
}
extern "C" fn pull_callback<
T: ToNapiValue + Send + 'static,
S: Stream<Item = Result<T>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
match pull_callback_impl::<T, S>(env, info) {
Ok(val) => val,
Err(err) => unsafe {
let js_error: JsError = err.into();
js_error.throw_into(env);
ptr::null_mut()
},
}
}
fn pull_callback_impl<
T: ToNapiValue + Send + 'static,
S: Stream<Item = Result<T>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> Result<sys::napi_value> {
let mut data = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_cb_info(
env,
info,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
&mut data,
)
},
"Get ReadableStream.pull callback info failed"
)?;
let mut stream: Pin<&mut S> = Pin::new(Box::leak(unsafe { Box::from_raw(data.cast()) }));
let env = Env::from_raw(env);
let promise = env.spawn_future_with_callback(
async move { stream.next().await.transpose() },
|env, val| {
let mut output = Object::new(env.raw())?;
if let Some(val) = val {
output.set("value", val)?;
output.set("done", false)?;
} else {
output.set("value", Null)?;
output.set("done", true)?;
}
unsafe {
crate::__private::log_js_value("log", env.0, [output.0.value]);
};
Ok(output.0.value)
},
)?;
Ok(promise.inner)
}
extern "C" fn pull_callback_bytes<
B: Into<Vec<u8>>,
S: Stream<Item = Result<B>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> sys::napi_value {
match pull_callback_impl_bytes::<B, S>(env, info) {
Ok(val) => val,
Err(err) => unsafe {
let js_error: JsError = err.into();
js_error.throw_into(env);
ptr::null_mut()
},
}
}
fn pull_callback_impl_bytes<
B: Into<Vec<u8>>,
S: Stream<Item = Result<B>> + Unpin + Send + 'static,
>(
env: sys::napi_env,
info: sys::napi_callback_info,
) -> Result<sys::napi_value> {
let mut data = ptr::null_mut();
let mut argc = 1;
let mut args = [ptr::null_mut(); 1];
check_status!(
unsafe {
sys::napi_get_cb_info(
env,
info,
&mut argc,
args.as_mut_ptr(),
ptr::null_mut(),
&mut data,
)
},
"Get ReadableStream.pull callback info failed"
)?;
let [controller] = args;
let controller = unsafe { Object::from_napi_value(env, controller)? };
let enqueue = controller
.get_named_property_unchecked::<Function<BufferSlice, ()>>("enqueue")?
.bind(&controller)?
.create_ref()?;
let close = controller
.get_named_property_unchecked::<Function<(), ()>>("close")?
.bind(&controller)?
.create_ref()?;
let mut stream: Pin<&mut S> = Pin::new(Box::leak(unsafe { Box::from_raw(data.cast()) }));
let env = Env::from_raw(env);
let promise = env.spawn_future_with_callback(
async move {
stream
.next()
.await
.transpose()
.map(|v| v.map(|v| Into::<Vec<u8>>::into(v)))
},
move |env, val| {
if let Some(val) = val {
let enqueue_fn = enqueue.borrow_back(&env)?;
enqueue_fn.call(BufferSlice::from_data(&env, val)?)?;
} else {
let close_fn = close.borrow_back(&env)?;
close_fn.call(())?;
}
drop(enqueue);
drop(close);
Ok(())
},
)?;
Ok(promise.inner)
}

View File

@ -0,0 +1,143 @@
use std::{marker::PhantomData, ptr};
use crate::{
bindgen_prelude::{
FromNapiValue, Function, PromiseRaw, ToNapiValue, TypeName, ValidateNapiValue,
},
check_status, sys, Env, Error, NapiRaw, Result, Status, ValueType,
};
pub struct WriteableStream<'env> {
pub(crate) value: sys::napi_value,
pub(crate) env: sys::napi_env,
pub(crate) _scope: &'env PhantomData<()>,
}
impl NapiRaw for WriteableStream<'_> {
unsafe fn raw(&self) -> sys::napi_value {
self.value
}
}
impl TypeName for WriteableStream<'_> {
fn type_name() -> &'static str {
"WriteableStream"
}
fn value_type() -> ValueType {
ValueType::Object
}
}
impl ValidateNapiValue for WriteableStream<'_> {
unsafe fn validate(
env: napi_sys::napi_env,
napi_val: napi_sys::napi_value,
) -> Result<napi_sys::napi_value> {
let constructor = Env::from(env)
.get_global()?
.get_named_property_unchecked::<Function>("WritableStream")?;
let mut is_instance = false;
check_status!(
unsafe { sys::napi_instanceof(env, napi_val, constructor.value, &mut is_instance) },
"Check WritableStream instance failed"
)?;
if !is_instance {
return Err(Error::new(
Status::InvalidArg,
"Value is not a WritableStream",
));
}
Ok(ptr::null_mut())
}
}
impl FromNapiValue for WriteableStream<'_> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
Ok(Self {
value: napi_val,
env,
_scope: &PhantomData,
})
}
}
impl WriteableStream<'_> {
pub fn ready(&self) -> Result<PromiseRaw<()>> {
let mut promise = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(self.env, self.value, c"ready".as_ptr().cast(), &mut promise)
},
"Get ready property failed"
)?;
Ok(PromiseRaw::new(self.env, promise))
}
/// The `abort()` method of the `WritableStream` interface aborts the stream,
/// signaling that the producer can no longer successfully write to the stream and it is to be immediately moved to an error state,
/// with any queued writes discarded.
pub fn abort(&mut self, reason: String) -> Result<PromiseRaw<()>> {
let mut abort_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
self.value,
c"abort".as_ptr().cast(),
&mut abort_fn,
)
},
"Get abort property failed"
)?;
let reason_value = unsafe { ToNapiValue::to_napi_value(self.env, reason)? };
let mut promise = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
self.value,
abort_fn,
1,
[reason_value].as_ptr(),
&mut promise,
)
},
"Call abort function failed"
)?;
Ok(PromiseRaw::new(self.env, promise))
}
/// The `close()` method of the `WritableStream` interface closes the associated stream.
///
/// All chunks written before this method is called are sent before the returned promise is fulfilled.
pub fn close(&mut self) -> Result<PromiseRaw<()>> {
let mut close_fn = ptr::null_mut();
check_status!(
unsafe {
sys::napi_get_named_property(
self.env,
self.value,
c"close".as_ptr().cast(),
&mut close_fn,
)
},
"Get close property failed"
)?;
let mut promise = ptr::null_mut();
check_status!(
unsafe {
sys::napi_call_function(
self.env,
self.value,
close_fn,
0,
ptr::null_mut(),
&mut promise,
)
},
"Call close function failed"
)?;
Ok(PromiseRaw::new(self.env, promise))
}
}

View File

@ -11,6 +11,8 @@ pub use module_register::*;
use super::sys;
use crate::{JsError, Result, Status};
#[cfg(feature = "tokio_rt")]
pub mod async_iterator;
mod callback_info;
mod env;
mod error;

View File

@ -333,7 +333,7 @@ impl Env {
finalize_callback: Finalize,
) -> Result<JsBufferValue>
where
Finalize: FnOnce(Hint, Env),
Finalize: FnOnce(Env, Hint),
{
let mut raw_value = ptr::null_mut();
if data.is_null() || data as *const u8 == EMPTY_VEC.as_ptr() {
@ -363,7 +363,7 @@ impl Env {
&mut raw_value,
);
data = result_data.cast();
finalize(hint, *self);
finalize(*self, hint);
check_status!(status)?;
} else {
check_status!(status)?;
@ -510,7 +510,7 @@ impl Env {
finalize_callback: Finalize,
) -> Result<JsArrayBufferValue>
where
Finalize: FnOnce(Hint, Env),
Finalize: FnOnce(Env, Hint),
{
let mut raw_value = ptr::null_mut();
let hint_ptr = Box::into_raw(Box::new((hint, finalize_callback)));
@ -543,7 +543,7 @@ impl Env {
let status =
sys::napi_create_arraybuffer(self.0, length, &mut underlying_data, &mut raw_value);
ptr::copy_nonoverlapping(data, underlying_data.cast(), length);
finalize(hint, *self);
finalize(*self, hint);
check_status!(status)?;
} else {
check_status!(status)?;
@ -1108,8 +1108,9 @@ impl Env {
/// So you can access the `Env` and resolved value after the future completed
pub fn spawn_future_with_callback<
T: 'static + Send + ToNapiValue,
V: ToNapiValue,
F: 'static + Send + Future<Output = Result<T>>,
R: 'static + FnOnce(&mut Env, &mut T) -> Result<()>,
R: 'static + FnOnce(Env, T) -> Result<V>,
>(
&self,
fut: F,
@ -1117,8 +1118,8 @@ impl Env {
) -> Result<PromiseRaw<T>> {
use crate::tokio_runtime;
let promise = tokio_runtime::execute_tokio_future(self.0, fut, move |env, mut val| unsafe {
callback(&mut Env::from_raw(env), &mut val)?;
let promise = tokio_runtime::execute_tokio_future(self.0, fut, move |env, val| unsafe {
let val = callback(Env::from_raw(env), val)?;
ToNapiValue::to_napi_value(env, val)
})?;
@ -1378,7 +1379,7 @@ impl Env {
}
/// This function could be used for `BufferSlice::from_external` and want do noting when Buffer finalized.
pub fn noop_finalize<Hint>(_hint: Hint, _env: Env) {}
pub fn noop_finalize<Hint>(_env: Env, _hint: Hint) {}
unsafe extern "C" fn drop_buffer(
_env: sys::napi_env,
@ -1443,10 +1444,10 @@ pub(crate) unsafe extern "C" fn raw_finalize_with_custom_callback<Hint, Finalize
_finalize_data: *mut c_void,
finalize_hint: *mut c_void,
) where
Finalize: FnOnce(Hint, Env),
Finalize: FnOnce(Env, Hint),
{
let (hint, callback) = unsafe { *Box::from_raw(finalize_hint as *mut (Hint, Finalize)) };
callback(hint, Env::from_raw(env));
callback(Env::from_raw(env), hint);
}
#[cfg(feature = "napi8")]

View File

@ -218,3 +218,8 @@ pub extern crate tokio;
#[cfg(feature = "error_anyhow")]
pub extern crate anyhow;
#[cfg(feature = "web_stream")]
pub extern crate futures_core;
#[cfg(feature = "web_stream")]
pub extern crate tokio_stream;

View File

@ -232,18 +232,30 @@ pub fn execute_tokio_future<
}
pub struct AsyncBlockBuilder<
V: ToNapiValue + Send + 'static,
V: Send + 'static,
F: Future<Output = Result<V>> + Send + 'static,
Dispose: FnOnce(Env) + 'static,
Dispose: FnOnce(Env) -> Result<()> + 'static = fn(Env) -> Result<()>,
> {
inner: F,
dispose: Option<Dispose>,
}
impl<V: ToNapiValue + Send + 'static, F: Future<Output = Result<V>> + Send + 'static>
AsyncBlockBuilder<V, F>
{
/// Create a new `AsyncBlockBuilder` with the given future, without dispose
pub fn new(inner: F) -> Self {
Self {
inner,
dispose: None,
}
}
}
impl<
V: ToNapiValue + Send + 'static,
F: Future<Output = Result<V>> + Send + 'static,
Dispose: FnOnce(Env),
Dispose: FnOnce(Env) -> Result<()> + 'static,
> AsyncBlockBuilder<V, F, Dispose>
{
pub fn with(inner: F) -> Self {
@ -258,12 +270,12 @@ impl<
self
}
pub fn build(self, env: Env) -> Result<AsyncBlock<V>> {
pub fn build(self, env: &Env) -> Result<AsyncBlock<V>> {
Ok(AsyncBlock {
inner: execute_tokio_future(env.0, self.inner, |env, v| unsafe {
if let Some(dispose) = self.dispose {
let env = Env::from_raw(env);
dispose(env);
dispose(env)?;
}
V::to_napi_value(env, v)
})?,
@ -272,12 +284,29 @@ impl<
}
}
pub struct AsyncBlock<T: ToNapiValue + Send + 'static> {
impl<V: Send + 'static, F: Future<Output = Result<V>> + Send + 'static> AsyncBlockBuilder<V, F> {
/// Create a new `AsyncBlockBuilder` with the given future, without dispose
pub fn build_with_map<T: ToNapiValue, Map: FnOnce(Env, V) -> Result<T> + 'static>(
env: &Env,
inner: F,
map: Map,
) -> Result<AsyncBlock<T>> {
Ok(AsyncBlock {
inner: execute_tokio_future(env.0, inner, |env, v| unsafe {
let v = map(Env::from_raw(env), v)?;
T::to_napi_value(env, v)
})?,
_phantom: PhantomData,
})
}
}
pub struct AsyncBlock<T: ToNapiValue + 'static> {
inner: sys::napi_value,
_phantom: PhantomData<T>,
}
impl<T: ToNapiValue + Send + 'static> ToNapiValue for AsyncBlock<T> {
impl<T: ToNapiValue + 'static> ToNapiValue for AsyncBlock<T> {
unsafe fn to_napi_value(_: napi_sys::napi_env, val: Self) -> Result<napi_sys::napi_value> {
Ok(val.inner)
}

View File

@ -54,7 +54,7 @@ pub fn create_borrowed_buffer_with_finalize(env: Env) -> ContextlessResult<Buffe
data_ptr,
length,
manually_drop,
|mut hint: ManuallyDrop<Vec<u8>>, _| {
|_, mut hint: ManuallyDrop<Vec<u8>>| {
ManuallyDrop::drop(&mut hint);
},
)
@ -78,7 +78,7 @@ pub fn create_empty_borrowed_buffer_with_finalize(
data_ptr,
length,
manually_drop,
|mut hint: ManuallyDrop<Vec<u8>>, _| {
|_, mut hint: ManuallyDrop<Vec<u8>>| {
ManuallyDrop::drop(&mut hint);
},
)

View File

@ -1,8 +1,7 @@
use std::convert::TryInto;
use napi::{
bindgen_prelude::{Buffer, PromiseRaw},
CallContext, Env, Error, JsNumber, JsObject, Result, Task,
bindgen_prelude::Buffer, CallContext, Env, Error, JsNumber, JsObject, JsUnknown, Result, Task,
};
struct ComputeFib {
@ -36,11 +35,11 @@ fn fibonacci_native(n: u32) -> u32 {
}
#[js_function(1)]
fn test_spawn_thread(ctx: CallContext) -> Result<PromiseRaw<JsNumber>> {
fn test_spawn_thread(ctx: CallContext) -> Result<JsUnknown> {
let n = ctx.get::<JsNumber>(0)?;
let task = ComputeFib::new(n.try_into()?);
let async_promise = ctx.env.spawn(task)?;
Ok(async_promise.promise_object())
Ok(async_promise.promise_object().into_unknown())
}
struct CountBufferLength {
@ -74,11 +73,11 @@ impl Task for CountBufferLength {
}
#[js_function(1)]
fn test_spawn_thread_with_ref(ctx: CallContext) -> Result<PromiseRaw<JsNumber>> {
fn test_spawn_thread_with_ref(ctx: CallContext) -> Result<JsUnknown> {
let n = ctx.get::<Buffer>(0)?;
let task = CountBufferLength::new(n);
let async_work_promise = ctx.env.spawn(task)?;
Ok(async_work_promise.promise_object())
Ok(async_work_promise.promise_object().into_unknown())
}
pub fn register_js(exports: &mut JsObject) -> Result<()> {

View File

@ -16,6 +16,7 @@ error_try_builds = []
[dependencies]
chrono = "0.4"
futures = "0.3"
bytes = "1"
napi-derive = { path = "../../crates/macro", features = ["type-def"] }
napi-shared = { path = "../napi-shared" }
serde = "1"
@ -24,6 +25,8 @@ serde_derive = "1"
serde_json = "1"
indexmap = "2"
rustc-hash = "2"
tokio-stream = "0.1"
tokio-util = { version = "0.7", features = ["io"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
napi = { path = "../../crates/napi", default-features = false, features = [
@ -38,8 +41,10 @@ napi = { path = "../../crates/napi", default-features = false, features = [
"tokio_rt",
"tokio_fs",
"tokio_macros",
"tokio_io_util",
"deferred_trace",
"node_version_detect",
"web_stream",
] }
tokio = { version = "1", features = ["rt", "time"] }
@ -56,7 +61,9 @@ napi = { path = "../../crates/napi", default-features = false, features = [
"tokio_rt",
"tokio_macros",
"tokio_sync",
"tokio_io_util",
"deferred_trace",
"web_stream",
] }
tokio = { version = "1", default-features = false, features = ["rt", "time"] }

View File

@ -268,6 +268,8 @@ Generated by [AVA](https://avajs.dev).
export declare function acceptSlice(fixture: Uint8Array): bigint␊
export declare function acceptStream(stream: ReadableStream<Uint8Array>): Promise<Buffer>
export declare function acceptThreadsafeFunction(func: ((err: Error | null, arg: number) => any)): void␊
export declare function acceptThreadsafeFunctionFatal(func: ((arg: number) => void)): void␊
@ -431,6 +433,8 @@ Generated by [AVA](https://avajs.dev).
export declare function createOptionalExternal(size?: number | undefined | null): ExternalObject<number> | null␊
export declare function createReadableStream(): ReadableStream<Buffer>
export declare function createReferenceOnFunction(cb: () => void): Promise<void>
export declare function createSymbol(): symbol␊

View File

@ -2,6 +2,9 @@ import { Buffer } from 'node:buffer'
import { exec } from 'node:child_process'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createReadStream } from 'node:fs'
import { readFile as nodeReadFile } from 'node:fs/promises'
import { Readable } from 'node:stream'
import { Subject, take } from 'rxjs'
import Sinon, { spy } from 'sinon'
@ -215,6 +218,8 @@ import {
passSetWithHasherToJs,
Rule,
callRuleHandler,
acceptStream,
createReadableStream,
} from '../index.cjs'
import { test } from './test.framework.js'
@ -259,7 +264,7 @@ test('string', (t) => {
test('array', (t) => {
t.deepEqual(getNums(), [1, 1, 2, 3, 5, 8])
t.deepEqual(getWords(), ['foo', 'bar'])
t.deepEqual(getTuple([1, "test", 2]), 3)
t.deepEqual(getTuple([1, 'test', 2]), 3)
t.is(sumNums([1, 2, 3, 4, 5]), 15)
t.deepEqual(getNumArr(), [1, 2])
@ -1490,3 +1495,28 @@ test('type', (t) => {
}
t.is(callRuleHandler(rule, 1), 6)
})
test('acceptStream', async (t) => {
if (process.version.startsWith('v18') || process.env.WASI_TEST) {
// https://github.com/nodejs/node/issues/56432
t.pass('Skip when Node.js is 18 and WASI due to bug')
return
}
const selfPath = fileURLToPath(import.meta.url)
const nodeFileStream = createReadStream(selfPath)
const buffer = await acceptStream(Readable.toWeb(nodeFileStream))
t.is(buffer.toString('utf-8'), await nodeReadFile(selfPath, 'utf-8'))
})
test('create readable stream from channel', async (t) => {
if (process.env.WASI_TEST) {
t.pass('Skip when WASI due to bug')
return
}
const stream = await createReadableStream()
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
t.is(Buffer.concat(chunks).toString('utf-8'), 'hello'.repeat(100))
})

View File

@ -324,86 +324,88 @@ function __napi_rs_initialize_modules(__napiInstance) {
__napiInstance.exports['__napi_register__btree_set_to_rust_328']?.()
__napiInstance.exports['__napi_register__btree_set_to_js_329']?.()
__napiInstance.exports['__napi_register__return_from_shared_crate_330']?.()
__napiInstance.exports['__napi_register__contains_331']?.()
__napiInstance.exports['__napi_register__concat_str_332']?.()
__napiInstance.exports['__napi_register__concat_utf16_333']?.()
__napiInstance.exports['__napi_register__concat_latin1_334']?.()
__napiInstance.exports['__napi_register__roundtrip_str_335']?.()
__napiInstance.exports['__napi_register__return_c_string_336']?.()
__napiInstance.exports['__napi_register__set_symbol_in_obj_337']?.()
__napiInstance.exports['__napi_register__create_symbol_338']?.()
__napiInstance.exports['__napi_register__create_symbol_for_339']?.()
__napiInstance.exports['__napi_register__DelaySum_impl_340']?.()
__napiInstance.exports['__napi_register__without_abort_controller_341']?.()
__napiInstance.exports['__napi_register__with_abort_controller_342']?.()
__napiInstance.exports['__napi_register__AsyncTaskVoidReturn_impl_343']?.()
__napiInstance.exports['__napi_register__async_task_void_return_344']?.()
__napiInstance.exports['__napi_register__AsyncTaskOptionalReturn_impl_345']?.()
__napiInstance.exports['__napi_register__async_task_optional_return_346']?.()
__napiInstance.exports['__napi_register__AsyncTaskReadFile_impl_347']?.()
__napiInstance.exports['__napi_register__async_task_read_file_348']?.()
__napiInstance.exports['__napi_register__call_threadsafe_function_349']?.()
__napiInstance.exports['__napi_register__call_long_threadsafe_function_350']?.()
__napiInstance.exports['__napi_register__threadsafe_function_throw_error_351']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_352']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_353']?.()
__napiInstance.exports['__napi_register__threadsafe_function_closure_capture_354']?.()
__napiInstance.exports['__napi_register__tsfn_call_with_callback_355']?.()
__napiInstance.exports['__napi_register__tsfn_async_call_356']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_357']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_358']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_359']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_360']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_timeout_361']?.()
__napiInstance.exports['__napi_register__tsfn_throw_from_js_362']?.()
__napiInstance.exports['__napi_register__spawn_thread_in_thread_363']?.()
__napiInstance.exports['__napi_register__Pet_struct_364']?.()
__napiInstance.exports['__napi_register__tsfn_in_either_365']?.()
__napiInstance.exports['__napi_register__MyVec_struct_366']?.()
__napiInstance.exports['__napi_register__get_my_vec_367']?.()
__napiInstance.exports['__napi_register__CustomU32_368']?.()
__napiInstance.exports['__napi_register__MyPromise_369']?.()
__napiInstance.exports['__napi_register__Nullable_370']?.()
__napiInstance.exports['__napi_register__VoidNullable_371']?.()
__napiInstance.exports['__napi_register__RuleHandler_372']?.()
__napiInstance.exports['__napi_register__Rule_struct_373']?.()
__napiInstance.exports['__napi_register__call_rule_handler_374']?.()
__napiInstance.exports['__napi_register__get_buffer_375']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_376']?.()
__napiInstance.exports['__napi_register__append_buffer_377']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_378']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_379']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_380']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_381']?.()
__napiInstance.exports['__napi_register__convert_u32_array_382']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_383']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_384']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_385']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_386']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_387']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_388']?.()
__napiInstance.exports['__napi_register__accept_slice_389']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_390']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_391']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_392']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_393']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_394']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_395']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_396']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_397']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_398']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_399']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_400']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_401']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_402']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_403']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_404']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_405']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_406']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_407']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_408']?.()
__napiInstance.exports['__napi_register__Reader_struct_409']?.()
__napiInstance.exports['__napi_register__Reader_impl_411']?.()
__napiInstance.exports['__napi_register__accept_stream_331']?.()
__napiInstance.exports['__napi_register__create_readable_stream_332']?.()
__napiInstance.exports['__napi_register__contains_333']?.()
__napiInstance.exports['__napi_register__concat_str_334']?.()
__napiInstance.exports['__napi_register__concat_utf16_335']?.()
__napiInstance.exports['__napi_register__concat_latin1_336']?.()
__napiInstance.exports['__napi_register__roundtrip_str_337']?.()
__napiInstance.exports['__napi_register__return_c_string_338']?.()
__napiInstance.exports['__napi_register__set_symbol_in_obj_339']?.()
__napiInstance.exports['__napi_register__create_symbol_340']?.()
__napiInstance.exports['__napi_register__create_symbol_for_341']?.()
__napiInstance.exports['__napi_register__DelaySum_impl_342']?.()
__napiInstance.exports['__napi_register__without_abort_controller_343']?.()
__napiInstance.exports['__napi_register__with_abort_controller_344']?.()
__napiInstance.exports['__napi_register__AsyncTaskVoidReturn_impl_345']?.()
__napiInstance.exports['__napi_register__async_task_void_return_346']?.()
__napiInstance.exports['__napi_register__AsyncTaskOptionalReturn_impl_347']?.()
__napiInstance.exports['__napi_register__async_task_optional_return_348']?.()
__napiInstance.exports['__napi_register__AsyncTaskReadFile_impl_349']?.()
__napiInstance.exports['__napi_register__async_task_read_file_350']?.()
__napiInstance.exports['__napi_register__call_threadsafe_function_351']?.()
__napiInstance.exports['__napi_register__call_long_threadsafe_function_352']?.()
__napiInstance.exports['__napi_register__threadsafe_function_throw_error_353']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_354']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_355']?.()
__napiInstance.exports['__napi_register__threadsafe_function_closure_capture_356']?.()
__napiInstance.exports['__napi_register__tsfn_call_with_callback_357']?.()
__napiInstance.exports['__napi_register__tsfn_async_call_358']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_359']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_360']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_361']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_362']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_timeout_363']?.()
__napiInstance.exports['__napi_register__tsfn_throw_from_js_364']?.()
__napiInstance.exports['__napi_register__spawn_thread_in_thread_365']?.()
__napiInstance.exports['__napi_register__Pet_struct_366']?.()
__napiInstance.exports['__napi_register__tsfn_in_either_367']?.()
__napiInstance.exports['__napi_register__MyVec_struct_368']?.()
__napiInstance.exports['__napi_register__get_my_vec_369']?.()
__napiInstance.exports['__napi_register__CustomU32_370']?.()
__napiInstance.exports['__napi_register__MyPromise_371']?.()
__napiInstance.exports['__napi_register__Nullable_372']?.()
__napiInstance.exports['__napi_register__VoidNullable_373']?.()
__napiInstance.exports['__napi_register__RuleHandler_374']?.()
__napiInstance.exports['__napi_register__Rule_struct_375']?.()
__napiInstance.exports['__napi_register__call_rule_handler_376']?.()
__napiInstance.exports['__napi_register__get_buffer_377']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_378']?.()
__napiInstance.exports['__napi_register__append_buffer_379']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_380']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_381']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_382']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_383']?.()
__napiInstance.exports['__napi_register__convert_u32_array_384']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_385']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_386']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_387']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_388']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_389']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_390']?.()
__napiInstance.exports['__napi_register__accept_slice_391']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_392']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_393']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_394']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_395']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_396']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_397']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_398']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_399']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_400']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_401']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_402']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_403']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_404']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_405']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_406']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_407']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_408']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_409']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_410']?.()
__napiInstance.exports['__napi_register__Reader_struct_411']?.()
__napiInstance.exports['__napi_register__Reader_impl_413']?.()
}
export const Animal = __napiModule.exports.Animal
export const AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor
@ -449,6 +451,7 @@ export const UseNullableClass = __napiModule.exports.UseNullableClass
export const Width = __napiModule.exports.Width
export const acceptArraybuffer = __napiModule.exports.acceptArraybuffer
export const acceptSlice = __napiModule.exports.acceptSlice
export const acceptStream = __napiModule.exports.acceptStream
export const acceptThreadsafeFunction = __napiModule.exports.acceptThreadsafeFunction
export const acceptThreadsafeFunctionFatal = __napiModule.exports.acceptThreadsafeFunctionFatal
export const acceptThreadsafeFunctionTupleArgs = __napiModule.exports.acceptThreadsafeFunctionTupleArgs
@ -521,6 +524,7 @@ export const createObj = __napiModule.exports.createObj
export const createObjectWithClassField = __napiModule.exports.createObjectWithClassField
export const createObjWithProperty = __napiModule.exports.createObjWithProperty
export const createOptionalExternal = __napiModule.exports.createOptionalExternal
export const createReadableStream = __napiModule.exports.createReadableStream
export const createReferenceOnFunction = __napiModule.exports.createReferenceOnFunction
export const createSymbol = __napiModule.exports.createSymbol
export const createSymbolFor = __napiModule.exports.createSymbolFor

View File

@ -348,86 +348,88 @@ function __napi_rs_initialize_modules(__napiInstance) {
__napiInstance.exports['__napi_register__btree_set_to_rust_328']?.()
__napiInstance.exports['__napi_register__btree_set_to_js_329']?.()
__napiInstance.exports['__napi_register__return_from_shared_crate_330']?.()
__napiInstance.exports['__napi_register__contains_331']?.()
__napiInstance.exports['__napi_register__concat_str_332']?.()
__napiInstance.exports['__napi_register__concat_utf16_333']?.()
__napiInstance.exports['__napi_register__concat_latin1_334']?.()
__napiInstance.exports['__napi_register__roundtrip_str_335']?.()
__napiInstance.exports['__napi_register__return_c_string_336']?.()
__napiInstance.exports['__napi_register__set_symbol_in_obj_337']?.()
__napiInstance.exports['__napi_register__create_symbol_338']?.()
__napiInstance.exports['__napi_register__create_symbol_for_339']?.()
__napiInstance.exports['__napi_register__DelaySum_impl_340']?.()
__napiInstance.exports['__napi_register__without_abort_controller_341']?.()
__napiInstance.exports['__napi_register__with_abort_controller_342']?.()
__napiInstance.exports['__napi_register__AsyncTaskVoidReturn_impl_343']?.()
__napiInstance.exports['__napi_register__async_task_void_return_344']?.()
__napiInstance.exports['__napi_register__AsyncTaskOptionalReturn_impl_345']?.()
__napiInstance.exports['__napi_register__async_task_optional_return_346']?.()
__napiInstance.exports['__napi_register__AsyncTaskReadFile_impl_347']?.()
__napiInstance.exports['__napi_register__async_task_read_file_348']?.()
__napiInstance.exports['__napi_register__call_threadsafe_function_349']?.()
__napiInstance.exports['__napi_register__call_long_threadsafe_function_350']?.()
__napiInstance.exports['__napi_register__threadsafe_function_throw_error_351']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_352']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_353']?.()
__napiInstance.exports['__napi_register__threadsafe_function_closure_capture_354']?.()
__napiInstance.exports['__napi_register__tsfn_call_with_callback_355']?.()
__napiInstance.exports['__napi_register__tsfn_async_call_356']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_357']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_358']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_359']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_360']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_timeout_361']?.()
__napiInstance.exports['__napi_register__tsfn_throw_from_js_362']?.()
__napiInstance.exports['__napi_register__spawn_thread_in_thread_363']?.()
__napiInstance.exports['__napi_register__Pet_struct_364']?.()
__napiInstance.exports['__napi_register__tsfn_in_either_365']?.()
__napiInstance.exports['__napi_register__MyVec_struct_366']?.()
__napiInstance.exports['__napi_register__get_my_vec_367']?.()
__napiInstance.exports['__napi_register__CustomU32_368']?.()
__napiInstance.exports['__napi_register__MyPromise_369']?.()
__napiInstance.exports['__napi_register__Nullable_370']?.()
__napiInstance.exports['__napi_register__VoidNullable_371']?.()
__napiInstance.exports['__napi_register__RuleHandler_372']?.()
__napiInstance.exports['__napi_register__Rule_struct_373']?.()
__napiInstance.exports['__napi_register__call_rule_handler_374']?.()
__napiInstance.exports['__napi_register__get_buffer_375']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_376']?.()
__napiInstance.exports['__napi_register__append_buffer_377']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_378']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_379']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_380']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_381']?.()
__napiInstance.exports['__napi_register__convert_u32_array_382']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_383']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_384']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_385']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_386']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_387']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_388']?.()
__napiInstance.exports['__napi_register__accept_slice_389']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_390']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_391']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_392']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_393']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_394']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_395']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_396']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_397']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_398']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_399']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_400']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_401']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_402']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_403']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_404']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_405']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_406']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_407']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_408']?.()
__napiInstance.exports['__napi_register__Reader_struct_409']?.()
__napiInstance.exports['__napi_register__Reader_impl_411']?.()
__napiInstance.exports['__napi_register__accept_stream_331']?.()
__napiInstance.exports['__napi_register__create_readable_stream_332']?.()
__napiInstance.exports['__napi_register__contains_333']?.()
__napiInstance.exports['__napi_register__concat_str_334']?.()
__napiInstance.exports['__napi_register__concat_utf16_335']?.()
__napiInstance.exports['__napi_register__concat_latin1_336']?.()
__napiInstance.exports['__napi_register__roundtrip_str_337']?.()
__napiInstance.exports['__napi_register__return_c_string_338']?.()
__napiInstance.exports['__napi_register__set_symbol_in_obj_339']?.()
__napiInstance.exports['__napi_register__create_symbol_340']?.()
__napiInstance.exports['__napi_register__create_symbol_for_341']?.()
__napiInstance.exports['__napi_register__DelaySum_impl_342']?.()
__napiInstance.exports['__napi_register__without_abort_controller_343']?.()
__napiInstance.exports['__napi_register__with_abort_controller_344']?.()
__napiInstance.exports['__napi_register__AsyncTaskVoidReturn_impl_345']?.()
__napiInstance.exports['__napi_register__async_task_void_return_346']?.()
__napiInstance.exports['__napi_register__AsyncTaskOptionalReturn_impl_347']?.()
__napiInstance.exports['__napi_register__async_task_optional_return_348']?.()
__napiInstance.exports['__napi_register__AsyncTaskReadFile_impl_349']?.()
__napiInstance.exports['__napi_register__async_task_read_file_350']?.()
__napiInstance.exports['__napi_register__call_threadsafe_function_351']?.()
__napiInstance.exports['__napi_register__call_long_threadsafe_function_352']?.()
__napiInstance.exports['__napi_register__threadsafe_function_throw_error_353']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_354']?.()
__napiInstance.exports['__napi_register__threadsafe_function_fatal_mode_error_355']?.()
__napiInstance.exports['__napi_register__threadsafe_function_closure_capture_356']?.()
__napiInstance.exports['__napi_register__tsfn_call_with_callback_357']?.()
__napiInstance.exports['__napi_register__tsfn_async_call_358']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_359']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_fatal_360']?.()
__napiInstance.exports['__napi_register__accept_threadsafe_function_tuple_args_361']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_362']?.()
__napiInstance.exports['__napi_register__tsfn_return_promise_timeout_363']?.()
__napiInstance.exports['__napi_register__tsfn_throw_from_js_364']?.()
__napiInstance.exports['__napi_register__spawn_thread_in_thread_365']?.()
__napiInstance.exports['__napi_register__Pet_struct_366']?.()
__napiInstance.exports['__napi_register__tsfn_in_either_367']?.()
__napiInstance.exports['__napi_register__MyVec_struct_368']?.()
__napiInstance.exports['__napi_register__get_my_vec_369']?.()
__napiInstance.exports['__napi_register__CustomU32_370']?.()
__napiInstance.exports['__napi_register__MyPromise_371']?.()
__napiInstance.exports['__napi_register__Nullable_372']?.()
__napiInstance.exports['__napi_register__VoidNullable_373']?.()
__napiInstance.exports['__napi_register__RuleHandler_374']?.()
__napiInstance.exports['__napi_register__Rule_struct_375']?.()
__napiInstance.exports['__napi_register__call_rule_handler_376']?.()
__napiInstance.exports['__napi_register__get_buffer_377']?.()
__napiInstance.exports['__napi_register__get_buffer_slice_378']?.()
__napiInstance.exports['__napi_register__append_buffer_379']?.()
__napiInstance.exports['__napi_register__get_empty_buffer_380']?.()
__napiInstance.exports['__napi_register__create_external_buffer_slice_381']?.()
__napiInstance.exports['__napi_register__create_buffer_slice_from_copied_data_382']?.()
__napiInstance.exports['__napi_register__get_empty_typed_array_383']?.()
__napiInstance.exports['__napi_register__convert_u32_array_384']?.()
__napiInstance.exports['__napi_register__create_external_typed_array_385']?.()
__napiInstance.exports['__napi_register__mutate_typed_array_386']?.()
__napiInstance.exports['__napi_register__deref_uint8_array_387']?.()
__napiInstance.exports['__napi_register__buffer_pass_through_388']?.()
__napiInstance.exports['__napi_register__buffer_with_async_block_389']?.()
__napiInstance.exports['__napi_register__array_buffer_pass_through_390']?.()
__napiInstance.exports['__napi_register__accept_slice_391']?.()
__napiInstance.exports['__napi_register__accept_arraybuffer_392']?.()
__napiInstance.exports['__napi_register__create_arraybuffer_393']?.()
__napiInstance.exports['__napi_register__u8_array_to_array_394']?.()
__napiInstance.exports['__napi_register__i8_array_to_array_395']?.()
__napiInstance.exports['__napi_register__u16_array_to_array_396']?.()
__napiInstance.exports['__napi_register__i16_array_to_array_397']?.()
__napiInstance.exports['__napi_register__u32_array_to_array_398']?.()
__napiInstance.exports['__napi_register__i32_array_to_array_399']?.()
__napiInstance.exports['__napi_register__f32_array_to_array_400']?.()
__napiInstance.exports['__napi_register__f64_array_to_array_401']?.()
__napiInstance.exports['__napi_register__u64_array_to_array_402']?.()
__napiInstance.exports['__napi_register__i64_array_to_array_403']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_404']?.()
__napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_405']?.()
__napiInstance.exports['__napi_register__AsyncBuffer_impl_406']?.()
__napiInstance.exports['__napi_register__async_reduce_buffer_407']?.()
__napiInstance.exports['__napi_register__async_buffer_to_array_408']?.()
__napiInstance.exports['__napi_register__u_init8_array_from_string_409']?.()
__napiInstance.exports['__napi_register__AsyncReader_impl_410']?.()
__napiInstance.exports['__napi_register__Reader_struct_411']?.()
__napiInstance.exports['__napi_register__Reader_impl_413']?.()
}
module.exports.Animal = __napiModule.exports.Animal
module.exports.AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor
@ -473,6 +475,7 @@ module.exports.UseNullableClass = __napiModule.exports.UseNullableClass
module.exports.Width = __napiModule.exports.Width
module.exports.acceptArraybuffer = __napiModule.exports.acceptArraybuffer
module.exports.acceptSlice = __napiModule.exports.acceptSlice
module.exports.acceptStream = __napiModule.exports.acceptStream
module.exports.acceptThreadsafeFunction = __napiModule.exports.acceptThreadsafeFunction
module.exports.acceptThreadsafeFunctionFatal = __napiModule.exports.acceptThreadsafeFunctionFatal
module.exports.acceptThreadsafeFunctionTupleArgs = __napiModule.exports.acceptThreadsafeFunctionTupleArgs
@ -545,6 +548,7 @@ module.exports.createObj = __napiModule.exports.createObj
module.exports.createObjectWithClassField = __napiModule.exports.createObjectWithClassField
module.exports.createObjWithProperty = __napiModule.exports.createObjWithProperty
module.exports.createOptionalExternal = __napiModule.exports.createOptionalExternal
module.exports.createReadableStream = __napiModule.exports.createReadableStream
module.exports.createReferenceOnFunction = __napiModule.exports.createReferenceOnFunction
module.exports.createSymbol = __napiModule.exports.createSymbol
module.exports.createSymbolFor = __napiModule.exports.createSymbolFor

View File

@ -408,6 +408,7 @@ module.exports.UseNullableClass = nativeBinding.UseNullableClass
module.exports.Width = nativeBinding.Width
module.exports.acceptArraybuffer = nativeBinding.acceptArraybuffer
module.exports.acceptSlice = nativeBinding.acceptSlice
module.exports.acceptStream = nativeBinding.acceptStream
module.exports.acceptThreadsafeFunction = nativeBinding.acceptThreadsafeFunction
module.exports.acceptThreadsafeFunctionFatal = nativeBinding.acceptThreadsafeFunctionFatal
module.exports.acceptThreadsafeFunctionTupleArgs = nativeBinding.acceptThreadsafeFunctionTupleArgs
@ -480,6 +481,7 @@ module.exports.createObj = nativeBinding.createObj
module.exports.createObjectWithClassField = nativeBinding.createObjectWithClassField
module.exports.createObjWithProperty = nativeBinding.createObjWithProperty
module.exports.createOptionalExternal = nativeBinding.createOptionalExternal
module.exports.createReadableStream = nativeBinding.createReadableStream
module.exports.createReferenceOnFunction = nativeBinding.createReferenceOnFunction
module.exports.createSymbol = nativeBinding.createSymbol
module.exports.createSymbolFor = nativeBinding.createSymbolFor

View File

@ -258,6 +258,8 @@ export declare function acceptArraybuffer(fixture: ArrayBuffer): bigint
export declare function acceptSlice(fixture: Uint8Array): bigint
export declare function acceptStream(stream: ReadableStream<Uint8Array>): Promise<Buffer>
export declare function acceptThreadsafeFunction(func: ((err: Error | null, arg: number) => any)): void
export declare function acceptThreadsafeFunctionFatal(func: ((arg: number) => void)): void
@ -421,6 +423,8 @@ export declare function createObjWithProperty(): { value: ArrayBuffer, get gette
export declare function createOptionalExternal(size?: number | undefined | null): ExternalObject<number> | null
export declare function createReadableStream(): ReadableStream<Buffer>
export declare function createReferenceOnFunction(cb: () => void): Promise<void>
export declare function createSymbol(): symbol

View File

@ -76,7 +76,7 @@ fn callback_return_promise<T: Fn() -> Result<JsUnknown>>(
#[napi(ts_return_type = "Promise<string>")]
pub fn callback_return_promise_and_spawn<F: Fn(String) -> Result<Promise<String>>>(
env: Env,
env: &Env,
js_func: F,
) -> napi::Result<PromiseRaw<String>> {
let promise = js_func("Hello".to_owned())?;

View File

@ -52,7 +52,10 @@ pub fn call_function_with_arg(
}
#[napi(ts_return_type = "Promise<void>")]
pub fn create_reference_on_function(env: Env, cb: Function<(), ()>) -> Result<PromiseRaw<()>> {
pub fn create_reference_on_function<'env>(
env: &'env Env,
cb: Function<'env, (), ()>,
) -> Result<PromiseRaw<'env, ()>> {
let reference = cb.create_ref()?;
env.spawn_future_with_callback(
async {
@ -60,7 +63,7 @@ pub fn create_reference_on_function(env: Env, cb: Function<(), ()>) -> Result<Pr
Ok(())
},
move |env, _| {
let cb = reference.borrow_back(env)?;
let cb = reference.borrow_back(&env)?;
cb.call(())?;
Ok(())
},

View File

@ -64,6 +64,7 @@ mod reference;
mod serde;
mod set;
mod shared;
mod stream;
mod string;
mod symbol;
mod task;

View File

@ -7,12 +7,12 @@ pub async fn async_plus_100(p: Promise<u32>) -> Result<u32> {
}
#[napi]
pub fn call_then_on_promise(mut input: PromiseRaw<u32>) -> Result<PromiseRaw<String>> {
pub fn call_then_on_promise(input: PromiseRaw<u32>) -> Result<PromiseRaw<String>> {
input.then(|v| Ok(format!("{}", v.value)))
}
#[napi]
pub fn call_catch_on_promise(mut input: PromiseRaw<u32>) -> Result<PromiseRaw<String>> {
pub fn call_catch_on_promise(input: PromiseRaw<'_, u32>) -> Result<PromiseRaw<'_, String>> {
input.catch(|e: CallbackContext<String>| Ok(e.value))
}

View File

@ -0,0 +1,58 @@
use bytes::BytesMut;
use napi::bindgen_prelude::*;
use tokio::sync::mpsc::error::TrySendError;
use tokio_stream::{wrappers::ReceiverStream, StreamExt};
use tokio_util::io::{read_buf, StreamReader};
#[napi]
pub fn accept_stream(
env: &Env,
stream: ReadableStream<Uint8Array>,
) -> Result<AsyncBlock<BufferSlice<'static>>> {
let web_readable_stream = stream.read()?;
let mut input = StreamReader::new(web_readable_stream.map(|chunk| {
chunk
.map(bytes::Bytes::from_owner)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.reason))
}));
AsyncBlockBuilder::build_with_map(
env,
async move {
let mut bytes_mut = BytesMut::new();
loop {
let n = read_buf(&mut input, &mut bytes_mut).await?;
if n == 0 {
break;
}
}
Ok(bytes_mut)
},
|env, mut value| {
let value_ptr = value.as_mut_ptr();
unsafe {
BufferSlice::from_external(&env, value_ptr, value.len(), value, move |_, bytes| {
drop(bytes);
})
}
},
)
}
#[napi]
pub fn create_readable_stream(env: &Env) -> Result<ReadableStream<BufferSlice>> {
let (tx, rx) = tokio::sync::mpsc::channel(100);
std::thread::spawn(move || {
for _ in 0..100 {
match tx.try_send(Ok(b"hello".to_vec())) {
Err(TrySendError::Closed(_)) => {
panic!("closed");
}
Err(TrySendError::Full(_)) => {
panic!("queue is full");
}
Ok(_) => {}
}
}
});
ReadableStream::create_with_stream_bytes(env, ReceiverStream::new(rx))
}

View File

@ -100,10 +100,10 @@ pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Re
}
#[napi(ts_return_type = "Promise<void>")]
pub fn tsfn_async_call(
env: Env,
pub fn tsfn_async_call<'env>(
env: &'env Env,
func: Function<FnArgs<(u32, u32, u32)>, String>,
) -> napi::Result<PromiseRaw<()>> {
) -> napi::Result<PromiseRaw<'env, ()>> {
let tsfn = func.build_threadsafe_function().build()?;
env.spawn_future(async move {

View File

@ -32,7 +32,7 @@ pub fn create_external_buffer_slice(env: &Env) -> Result<BufferSlice> {
// Mock the ffi data that not managed by Rust
std::mem::forget(data);
unsafe {
BufferSlice::from_external(env, data_ptr, len, data_ptr, move |ptr, _| {
BufferSlice::from_external(env, data_ptr, len, data_ptr, move |_, ptr| {
std::mem::drop(Vec::from_raw_parts(ptr, len, len));
})
}
@ -76,11 +76,12 @@ async fn buffer_pass_through(buf: Buffer) -> Result<Buffer> {
}
#[napi]
fn buffer_with_async_block(env: Env, buf: Arc<Buffer>) -> Result<AsyncBlock<u32>> {
fn buffer_with_async_block(env: &Env, buf: Arc<Buffer>) -> Result<AsyncBlock<u32>> {
let buf_to_dispose = buf.clone();
AsyncBlockBuilder::with(async move { Ok(buf.len() as u32) })
.with_dispose(move |_| {
drop(buf_to_dispose);
Ok(())
})
.build(env)
}

View File

@ -42,7 +42,7 @@ pub struct Room {
}
#[napi]
pub fn test_async(env: Env) -> napi::Result<napi::bindgen_prelude::PromiseRaw<String>> {
pub fn test_async(env: &Env) -> napi::Result<napi::bindgen_prelude::PromiseRaw<String>> {
let data = serde_json::json!({
"findFirstBooking": {
"id": "ckovh15xa104945sj64rdk8oas",