diff --git a/bench/src/async_compute.rs b/bench/src/async_compute.rs index fea15081..2ff9569b 100644 --- a/bench/src/async_compute.rs +++ b/bench/src/async_compute.rs @@ -17,11 +17,11 @@ impl Task for BufferLength { } #[js_function(1)] -fn bench_async_task(ctx: CallContext) -> Result> { +fn bench_async_task(ctx: CallContext) -> Result { let n = ctx.get::(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)] diff --git a/cli/esbuild.mjs b/cli/esbuild.mjs index 65e9e5f8..8ae92293 100644 --- a/cli/esbuild.mjs +++ b/cli/esbuild.mjs @@ -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'], diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index 741502e1..95d180a2 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -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); } } } diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 780e4451..9e1612d5 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -190,6 +190,7 @@ static KNOWN_TYPES: LazyLock> ("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)) diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index cb82edca..3bcc780b 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -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::>() .join(", "); - format!("{}<{}>", self.ident.to_string(), types) + format!("{}<{}>", self.ident, types) } else { self.ident.to_string() } diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index a92f40b0..4eea95a0 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -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" } diff --git a/crates/napi/src/bindgen_runtime/async_iterator.rs b/crates/napi/src/bindgen_runtime/async_iterator.rs new file mode 100644 index 00000000..350efd19 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/async_iterator.rs @@ -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()` + /// + fn next( + &mut self, + value: Option, + ) -> impl Future>> + Send + 'static; + + #[allow(unused_variables)] + /// Implement complete to handle the `AsyncGenerator.return()` + /// + fn complete( + &mut self, + value: Option, + ) -> impl Future>> + Send + 'static { + async move { Ok(None) } + } + + #[allow(unused_variables)] + /// Implement catch to handle the `AsyncGenerator.throw()` + /// + fn catch( + &mut self, + env: Env, + value: Unknown, + ) -> impl Future>> + Send + 'static { + let err = value.into(); + async move { Err(err) } + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn create_async_iterator( + 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::), + 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( + 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::), + 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::), + 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::), + 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( + env: sys::napi_env, + info: sys::napi_callback_info, +) -> sys::napi_value { + match generator_next_fn::(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( + env: sys::napi_env, + info: sys::napi_callback_info, +) -> crate::Result { + 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> = 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( + 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( + 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() + } + } +} diff --git a/crates/napi/src/bindgen_runtime/callback_info.rs b/crates/napi/src/bindgen_runtime/callback_info.rs index 13e9468f..64d27ac3 100644 --- a/crates/napi/src/bindgen_runtime/callback_info.rs +++ b/crates/napi/src/bindgen_runtime/callback_info.rs @@ -146,7 +146,7 @@ impl CallbackInfo { obj: T, ) -> Result { let (instance, generator_ptr) = self._construct::(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 CallbackInfo { obj: T, ) -> Result { 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) } diff --git a/crates/napi/src/bindgen_runtime/iterator.rs b/crates/napi/src/bindgen_runtime/iterator.rs index 6dab5b70..5af10d44 100644 --- a/crates/napi/src/bindgen_runtime/iterator.rs +++ b/crates/napi/src/bindgen_runtime/iterator.rs @@ -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( +pub unsafe fn create_iterator( env: sys::napi_env, instance: sys::napi_value, generator_ptr: *mut T, @@ -288,8 +289,8 @@ extern "C" fn generator_next( 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( ); 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( 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( 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(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(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(), ) }; } diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 79b0667e..9cef7eda 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -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::*; diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index b65235de..ebdccb58 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -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( + pub unsafe fn from_external( 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 diff --git a/crates/napi/src/bindgen_runtime/js_values/function.rs b/crates/napi/src/bindgen_runtime/js_values/function.rs index c411d891..f180601b 100644 --- a/crates/napi/src/bindgen_runtime/js_values/function.rs +++ b/crates/napi/src/bindgen_runtime/js_values/function.rs @@ -260,6 +260,39 @@ impl Function<'_, Args, Retur )?; unsafe { Return::from_napi_value(self.env, raw_return) } } + + /// Call `Function.bind` + pub fn bind(&self, this: T) -> Result> { + 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")] diff --git a/crates/napi/src/bindgen_runtime/js_values/promise.rs b/crates/napi/src/bindgen_runtime/js_values/promise.rs index 874a1663..07d96db8 100644 --- a/crates/napi/src/bindgen_runtime/js_values/promise.rs +++ b/crates/napi/src/bindgen_runtime/js_values/promise.rs @@ -59,7 +59,7 @@ unsafe impl Send for Promise {} impl FromNapiValue for Promise { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result { let (tx, rx) = channel(); - let mut promise_object = unsafe { PromiseRaw::::from_napi_value(env, napi_val)? }; + let promise_object = unsafe { PromiseRaw::::from_napi_value(env, napi_val)? }; let tx_box = Arc::new(Cell::new(Some(tx))); let tx_in_catch = tx_box.clone(); promise_object diff --git a/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs b/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs index 076d319a..7d765c83 100644 --- a/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs +++ b/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs @@ -10,25 +10,31 @@ use crate::{ }; use crate::{Env, Error, NapiRaw, NapiValue, Status}; -pub struct PromiseRaw { +use super::Unknown; + +pub struct PromiseRaw<'env, T> { pub(crate) inner: sys::napi_value, env: sys::napi_env, - _phantom: PhantomData, + _phantom: &'env PhantomData, } -impl PromiseRaw { +impl 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 PromiseRaw { +impl<'env, T: FromNapiValue> PromiseRaw<'env, T> { /// Promise.then method - pub fn then(&mut self, cb: Callback) -> Result> + pub fn then(&self, cb: Callback) -> Result> where U: ToNapiValue, Callback: 'static + FnOnce(CallbackContext) -> Result, @@ -88,12 +94,12 @@ impl PromiseRaw { Ok(PromiseRaw:: { env: self.env, inner: new_promise, - _phantom: PhantomData, + _phantom: &PhantomData, }) } /// Promise.catch method - pub fn catch(&mut self, cb: Callback) -> Result> + pub fn catch(&self, cb: Callback) -> Result> where E: FromNapiValue, U: ToNapiValue, @@ -154,12 +160,12 @@ impl PromiseRaw { Ok(PromiseRaw:: { env: self.env, inner: new_promise, - _phantom: PhantomData, + _phantom: &PhantomData, }) } /// Promise.finally method - pub fn finally(&mut self, cb: Callback) -> Result> + pub fn finally(&mut self, cb: Callback) -> Result> where U: ToNapiValue, Callback: 'static + FnOnce(Env) -> Result, @@ -203,7 +209,7 @@ impl PromiseRaw { Ok(Self { env: self.env, inner: new_promise, - _phantom: PhantomData, + _phantom: &PhantomData, }) } @@ -216,7 +222,7 @@ impl PromiseRaw { } } -impl TypeName for PromiseRaw { +impl TypeName for PromiseRaw<'_, T> { fn type_name() -> &'static str { "Promise" } @@ -226,7 +232,7 @@ impl TypeName for PromiseRaw { } } -impl ValidateNapiValue for PromiseRaw { +impl ValidateNapiValue for PromiseRaw<'_, T> { unsafe fn validate( env: napi_sys::napi_env, napi_val: napi_sys::napi_value, @@ -235,13 +241,13 @@ impl ValidateNapiValue for PromiseRaw { } } -impl NapiRaw for PromiseRaw { +impl NapiRaw for PromiseRaw<'_, T> { unsafe fn raw(&self) -> sys::napi_value { self.inner } } -impl NapiValue for PromiseRaw { +impl NapiValue for PromiseRaw<'_, T> { unsafe fn from_raw(env: napi_sys::napi_env, value: napi_sys::napi_value) -> Result { let mut is_promise = false; check_status!(unsafe { sys::napi_is_promise(env, value, &mut is_promise) })?; @@ -249,7 +255,7 @@ impl NapiValue for PromiseRaw { .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 NapiValue for PromiseRaw { Self { env, inner: value, - _phantom: PhantomData, + _phantom: &PhantomData, } } } diff --git a/crates/napi/src/bindgen_runtime/js_values/stream.rs b/crates/napi/src/bindgen_runtime/js_values/stream.rs new file mode 100644 index 00000000..e21bd13b --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/stream.rs @@ -0,0 +1,5 @@ +pub use read::*; +pub use write::*; + +mod read; +mod write; diff --git a/crates/napi/src/bindgen_runtime/js_values/stream/read.rs b/crates/napi/src/bindgen_runtime/js_values/stream/read.rs new file mode 100644 index 00000000..e7d2af5f --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/stream/read.rs @@ -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 NapiRaw for ReadableStream<'_, T> { + unsafe fn raw(&self) -> sys::napi_value { + self.value + } +} + +impl TypeName for ReadableStream<'_, T> { + fn type_name() -> &'static str { + "ReadableStream" + } + + fn value_type() -> ValueType { + ValueType::Object + } +} + +impl ValidateNapiValue for ReadableStream<'_, T> { + unsafe fn validate( + env: napi_sys::napi_env, + napi_val: napi_sys::napi_value, + ) -> Result { + let constructor = Env::from(env) + .get_global()? + .get_named_property_unchecked::("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 FromNapiValue for ReadableStream<'_, T> { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + Ok(Self { + value: napi_val, + env, + _marker: PhantomData, + }) + } +} + +impl ReadableStream<'_, T> { + /// Returns a boolean indicating whether or not the readable stream is locked to a reader. + pub fn locked(&self) -> Result { + 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) -> Result> { + 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 ReadableStream<'_, T> { + pub fn read(&self) -> Result> { + 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>>::from_napi_value(self.env, bind_read)? + } + .build_threadsafe_function() + .callee_handled::() + .weak::() + .build()?; + Ok(Reader { + inner: read_function, + state: Arc::new((RwLock::new(Ok(None)), AtomicBool::new(false))), + }) + } +} + +impl ReadableStream<'_, T> { + pub fn new> + Unpin + Send + 'static>( + env: &Env, + inner: S, + ) -> Result { + let global = env.get_global()?; + let constructor = global.get_named_property_unchecked::("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::), + 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>, + S: Stream> + Unpin + Send + 'static, + >( + env: &Env, + inner: S, + ) -> Result { + let global = env.get_global()?; + let constructor = global.get_named_property_unchecked::("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::), + 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, + done: bool, +} + +impl FromNapiValue for IteratorValue<'_, T> { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + 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 { + inner: ThreadsafeFunction<(), PromiseRaw<'static, IteratorValue<'static, T>>, (), true, true>, + state: Arc<(RwLock>>, AtomicBool)>, +} + +impl futures_core::Stream for Reader { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + 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| { + 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> + Unpin + Send + 'static, +>( + env: sys::napi_env, + info: sys::napi_callback_info, +) -> sys::napi_value { + match pull_callback_impl::(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> + Unpin + Send + 'static, +>( + env: sys::napi_env, + info: sys::napi_callback_info, +) -> Result { + 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>, + S: Stream> + Unpin + Send + 'static, +>( + env: sys::napi_env, + info: sys::napi_callback_info, +) -> sys::napi_value { + match pull_callback_impl_bytes::(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>, + S: Stream> + Unpin + Send + 'static, +>( + env: sys::napi_env, + info: sys::napi_callback_info, +) -> Result { + 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::>("enqueue")? + .bind(&controller)? + .create_ref()?; + let close = controller + .get_named_property_unchecked::>("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::>::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) +} diff --git a/crates/napi/src/bindgen_runtime/js_values/stream/write.rs b/crates/napi/src/bindgen_runtime/js_values/stream/write.rs new file mode 100644 index 00000000..76975da4 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/stream/write.rs @@ -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 { + let constructor = Env::from(env) + .get_global()? + .get_named_property_unchecked::("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 { + Ok(Self { + value: napi_val, + env, + _scope: &PhantomData, + }) + } +} + +impl WriteableStream<'_> { + pub fn ready(&self) -> Result> { + 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> { + 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> { + 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)) + } +} diff --git a/crates/napi/src/bindgen_runtime/mod.rs b/crates/napi/src/bindgen_runtime/mod.rs index 8077937b..660a7f6e 100644 --- a/crates/napi/src/bindgen_runtime/mod.rs +++ b/crates/napi/src/bindgen_runtime/mod.rs @@ -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; diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index edfb8280..c3a61506 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -333,7 +333,7 @@ impl Env { finalize_callback: Finalize, ) -> Result 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 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>, - R: 'static + FnOnce(&mut Env, &mut T) -> Result<()>, + R: 'static + FnOnce(Env, T) -> Result, >( &self, fut: F, @@ -1117,8 +1118,8 @@ impl Env { ) -> Result> { 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, _env: Env) {} +pub fn noop_finalize(_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> + Send + 'static, - Dispose: FnOnce(Env) + 'static, + Dispose: FnOnce(Env) -> Result<()> + 'static = fn(Env) -> Result<()>, > { inner: F, dispose: Option, } +impl> + Send + 'static> + AsyncBlockBuilder +{ + /// 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> + Send + 'static, - Dispose: FnOnce(Env), + Dispose: FnOnce(Env) -> Result<()> + 'static, > AsyncBlockBuilder { pub fn with(inner: F) -> Self { @@ -258,12 +270,12 @@ impl< self } - pub fn build(self, env: Env) -> Result> { + pub fn build(self, env: &Env) -> Result> { 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 { +impl> + Send + 'static> AsyncBlockBuilder { + /// Create a new `AsyncBlockBuilder` with the given future, without dispose + pub fn build_with_map Result + 'static>( + env: &Env, + inner: F, + map: Map, + ) -> Result> { + 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 { inner: sys::napi_value, _phantom: PhantomData, } -impl ToNapiValue for AsyncBlock { +impl ToNapiValue for AsyncBlock { unsafe fn to_napi_value(_: napi_sys::napi_env, val: Self) -> Result { Ok(val.inner) } diff --git a/examples/napi-compat-mode/src/buffer.rs b/examples/napi-compat-mode/src/buffer.rs index 4500682e..b3fbbb23 100644 --- a/examples/napi-compat-mode/src/buffer.rs +++ b/examples/napi-compat-mode/src/buffer.rs @@ -54,7 +54,7 @@ pub fn create_borrowed_buffer_with_finalize(env: Env) -> ContextlessResult>, _| { + |_, mut hint: ManuallyDrop>| { ManuallyDrop::drop(&mut hint); }, ) @@ -78,7 +78,7 @@ pub fn create_empty_borrowed_buffer_with_finalize( data_ptr, length, manually_drop, - |mut hint: ManuallyDrop>, _| { + |_, mut hint: ManuallyDrop>| { ManuallyDrop::drop(&mut hint); }, ) diff --git a/examples/napi-compat-mode/src/task.rs b/examples/napi-compat-mode/src/task.rs index 277e3375..26afd186 100644 --- a/examples/napi-compat-mode/src/task.rs +++ b/examples/napi-compat-mode/src/task.rs @@ -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> { +fn test_spawn_thread(ctx: CallContext) -> Result { let n = ctx.get::(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> { +fn test_spawn_thread_with_ref(ctx: CallContext) -> Result { let n = ctx.get::(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<()> { diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 020f1651..664331fe 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -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"] } diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index cf2e2b36..521c0afb 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -268,6 +268,8 @@ Generated by [AVA](https://avajs.dev). ␊ export declare function acceptSlice(fixture: Uint8Array): bigint␊ ␊ + export declare function acceptStream(stream: ReadableStream): Promise␊ + ␊ 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 | null␊ ␊ + export declare function createReadableStream(): ReadableStream␊ + ␊ export declare function createReferenceOnFunction(cb: () => void): Promise␊ ␊ export declare function createSymbol(): symbol␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index c37ccab7..13ffbc68 100644 Binary files a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap differ diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 21053be3..6334dc5f 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -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)) +}) diff --git a/examples/napi/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index 3cbff354..778af37f 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -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 diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index 5051a75f..55e50b8d 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -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 diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index 77549931..a19953ea 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -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 diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index f3d57e97..48d10747 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -258,6 +258,8 @@ export declare function acceptArraybuffer(fixture: ArrayBuffer): bigint export declare function acceptSlice(fixture: Uint8Array): bigint +export declare function acceptStream(stream: ReadableStream): Promise + 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 | null +export declare function createReadableStream(): ReadableStream + export declare function createReferenceOnFunction(cb: () => void): Promise export declare function createSymbol(): symbol diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs index 79e170ea..1b557cc7 100644 --- a/examples/napi/src/callback.rs +++ b/examples/napi/src/callback.rs @@ -76,7 +76,7 @@ fn callback_return_promise Result>( #[napi(ts_return_type = "Promise")] pub fn callback_return_promise_and_spawn Result>>( - env: Env, + env: &Env, js_func: F, ) -> napi::Result> { let promise = js_func("Hello".to_owned())?; diff --git a/examples/napi/src/function.rs b/examples/napi/src/function.rs index b902d737..d25a39a8 100644 --- a/examples/napi/src/function.rs +++ b/examples/napi/src/function.rs @@ -52,7 +52,10 @@ pub fn call_function_with_arg( } #[napi(ts_return_type = "Promise")] -pub fn create_reference_on_function(env: Env, cb: Function<(), ()>) -> Result> { +pub fn create_reference_on_function<'env>( + env: &'env Env, + cb: Function<'env, (), ()>, +) -> Result> { 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) -> Result { } #[napi] -pub fn call_then_on_promise(mut input: PromiseRaw) -> Result> { +pub fn call_then_on_promise(input: PromiseRaw) -> Result> { input.then(|v| Ok(format!("{}", v.value))) } #[napi] -pub fn call_catch_on_promise(mut input: PromiseRaw) -> Result> { +pub fn call_catch_on_promise(input: PromiseRaw<'_, u32>) -> Result> { input.catch(|e: CallbackContext| Ok(e.value)) } diff --git a/examples/napi/src/stream.rs b/examples/napi/src/stream.rs new file mode 100644 index 00000000..0a4867b9 --- /dev/null +++ b/examples/napi/src/stream.rs @@ -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, +) -> Result>> { + 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> { + 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)) +} diff --git a/examples/napi/src/threadsafe_function.rs b/examples/napi/src/threadsafe_function.rs index 63e25cf9..6166a920 100644 --- a/examples/napi/src/threadsafe_function.rs +++ b/examples/napi/src/threadsafe_function.rs @@ -100,10 +100,10 @@ pub fn tsfn_call_with_callback(tsfn: ThreadsafeFunction<(), String>) -> napi::Re } #[napi(ts_return_type = "Promise")] -pub fn tsfn_async_call( - env: Env, +pub fn tsfn_async_call<'env>( + env: &'env Env, func: Function, String>, -) -> napi::Result> { +) -> napi::Result> { let tsfn = func.build_threadsafe_function().build()?; env.spawn_future(async move { diff --git a/examples/napi/src/typed_array.rs b/examples/napi/src/typed_array.rs index 8aef5de1..c0bd5e04 100644 --- a/examples/napi/src/typed_array.rs +++ b/examples/napi/src/typed_array.rs @@ -32,7 +32,7 @@ pub fn create_external_buffer_slice(env: &Env) -> Result { // 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 { } #[napi] -fn buffer_with_async_block(env: Env, buf: Arc) -> Result> { +fn buffer_with_async_block(env: &Env, buf: Arc) -> Result> { 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) } diff --git a/memory-testing/src/lib.rs b/memory-testing/src/lib.rs index 9788310f..b02fbe35 100644 --- a/memory-testing/src/lib.rs +++ b/memory-testing/src/lib.rs @@ -42,7 +42,7 @@ pub struct Room { } #[napi] -pub fn test_async(env: Env) -> napi::Result> { +pub fn test_async(env: &Env) -> napi::Result> { let data = serde_json::json!({ "findFirstBooking": { "id": "ckovh15xa104945sj64rdk8oas",