diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 1299f621..75c4ccbd 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -410,6 +410,13 @@ pub fn ty_to_ts_type( Some((rust_ty, false)) } }); + } else if rust_ty == "AsyncBlock" { + if let Some(arg) = args.first() { + ts_ty = Some((format!("Promise<{}>", arg.0), false)); + } else { + // Not NAPI-RS `AsyncBlock` + ts_ty = Some((rust_ty, false)); + } } else if let Some(&(known_ty, _, _)) = KNOWN_TYPES.get(rust_ty.as_str()) { if rust_ty == "()" && is_return_ty { ts_ty = Some(("void".to_owned(), false)); diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index 7ff433d0..c24b0aa4 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -347,6 +347,7 @@ impl Drop for Buffer { /// SAFETY: This is undefined behavior, as the JS side may always modify the underlying buffer, /// without synchronization. Also see the docs for the `AsMut` impl. unsafe impl Send for Buffer {} +unsafe impl Sync for Buffer {} impl Clone for Buffer { fn clone(&self) -> Self { diff --git a/crates/napi/src/tokio_runtime.rs b/crates/napi/src/tokio_runtime.rs index 6e77678f..bdd3a333 100644 --- a/crates/napi/src/tokio_runtime.rs +++ b/crates/napi/src/tokio_runtime.rs @@ -6,7 +6,9 @@ use std::{ use tokio::runtime::Runtime; -use crate::{sys, Error, JsDeferred, JsUnknown, NapiValue, Result}; +use crate::{ + bindgen_runtime::ToNapiValue, sys, Env, Error, JsDeferred, JsUnknown, NapiValue, Result, +}; fn create_runtime() -> Option { #[cfg(not(target_family = "wasm"))] @@ -221,3 +223,55 @@ pub fn execute_tokio_future< Ok(promise.0.value) } + +pub struct AsyncBlockBuilder< + V: ToNapiValue + Send + 'static, + F: Future> + Send + 'static, + Dispose: FnOnce(Env) + 'static, +> { + inner: F, + dispose: Option, +} + +impl< + V: ToNapiValue + Send + 'static, + F: Future> + Send + 'static, + Dispose: FnOnce(Env), + > AsyncBlockBuilder +{ + pub fn with(inner: F) -> Self { + Self { + inner, + dispose: None, + } + } + + pub fn with_dispose(mut self, dispose: Dispose) -> Self { + self.dispose = Some(dispose); + self + } + + 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); + } + V::to_napi_value(env, v) + })?, + _phantom: PhantomData, + }) + } +} + +pub struct AsyncBlock { + inner: sys::napi_value, + _phantom: PhantomData, +} + +impl ToNapiValue for AsyncBlock { + unsafe fn to_napi_value(_: napi_sys::napi_env, val: Self) -> Result { + Ok(val.inner) + } +} diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index 833cce80..0fab6a4c 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -335,6 +335,8 @@ Generated by [AVA](https://avajs.dev). ␊ export declare function bufferPassThrough(buf: Buffer): Promise␊ ␊ + export declare function bufferWithAsyncBlock(buf: Buffer): Promise␊ + ␊ export declare function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void␊ ␊ export declare function buildThreadsafeFunctionFromFunctionCalleeHandle(callback: () => void): void␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index 3a3c8203..b4856e1d 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/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index 55b5efba..7077cc4d 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -359,29 +359,30 @@ function __napi_rs_initialize_modules(__napiInstance) { __napiInstance.exports['__napi_register__mutate_typed_array_363']?.() __napiInstance.exports['__napi_register__deref_uint8_array_364']?.() __napiInstance.exports['__napi_register__buffer_pass_through_365']?.() - __napiInstance.exports['__napi_register__array_buffer_pass_through_366']?.() - __napiInstance.exports['__napi_register__accept_slice_367']?.() - __napiInstance.exports['__napi_register__accept_arraybuffer_368']?.() - __napiInstance.exports['__napi_register__create_arraybuffer_369']?.() - __napiInstance.exports['__napi_register__u8_array_to_array_370']?.() - __napiInstance.exports['__napi_register__i8_array_to_array_371']?.() - __napiInstance.exports['__napi_register__u16_array_to_array_372']?.() - __napiInstance.exports['__napi_register__i16_array_to_array_373']?.() - __napiInstance.exports['__napi_register__u32_array_to_array_374']?.() - __napiInstance.exports['__napi_register__i32_array_to_array_375']?.() - __napiInstance.exports['__napi_register__f32_array_to_array_376']?.() - __napiInstance.exports['__napi_register__f64_array_to_array_377']?.() - __napiInstance.exports['__napi_register__u64_array_to_array_378']?.() - __napiInstance.exports['__napi_register__i64_array_to_array_379']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_380']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_381']?.() - __napiInstance.exports['__napi_register__AsyncBuffer_impl_382']?.() - __napiInstance.exports['__napi_register__async_reduce_buffer_383']?.() - __napiInstance.exports['__napi_register__async_buffer_to_array_384']?.() - __napiInstance.exports['__napi_register__u_init8_array_from_string_385']?.() - __napiInstance.exports['__napi_register__AsyncReader_impl_386']?.() - __napiInstance.exports['__napi_register__Reader_struct_387']?.() - __napiInstance.exports['__napi_register__Reader_impl_389']?.() + __napiInstance.exports['__napi_register__buffer_with_async_block_366']?.() + __napiInstance.exports['__napi_register__array_buffer_pass_through_367']?.() + __napiInstance.exports['__napi_register__accept_slice_368']?.() + __napiInstance.exports['__napi_register__accept_arraybuffer_369']?.() + __napiInstance.exports['__napi_register__create_arraybuffer_370']?.() + __napiInstance.exports['__napi_register__u8_array_to_array_371']?.() + __napiInstance.exports['__napi_register__i8_array_to_array_372']?.() + __napiInstance.exports['__napi_register__u16_array_to_array_373']?.() + __napiInstance.exports['__napi_register__i16_array_to_array_374']?.() + __napiInstance.exports['__napi_register__u32_array_to_array_375']?.() + __napiInstance.exports['__napi_register__i32_array_to_array_376']?.() + __napiInstance.exports['__napi_register__f32_array_to_array_377']?.() + __napiInstance.exports['__napi_register__f64_array_to_array_378']?.() + __napiInstance.exports['__napi_register__u64_array_to_array_379']?.() + __napiInstance.exports['__napi_register__i64_array_to_array_380']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_381']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_382']?.() + __napiInstance.exports['__napi_register__AsyncBuffer_impl_383']?.() + __napiInstance.exports['__napi_register__async_reduce_buffer_384']?.() + __napiInstance.exports['__napi_register__async_buffer_to_array_385']?.() + __napiInstance.exports['__napi_register__u_init8_array_from_string_386']?.() + __napiInstance.exports['__napi_register__AsyncReader_impl_387']?.() + __napiInstance.exports['__napi_register__Reader_struct_388']?.() + __napiInstance.exports['__napi_register__Reader_impl_390']?.() } export const Animal = __napiModule.exports.Animal export const AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor @@ -453,6 +454,7 @@ export const bigintGetU64AsString = __napiModule.exports.bigintGetU64AsString export const btreeSetToJs = __napiModule.exports.btreeSetToJs export const btreeSetToRust = __napiModule.exports.btreeSetToRust export const bufferPassThrough = __napiModule.exports.bufferPassThrough +export const bufferWithAsyncBlock = __napiModule.exports.bufferWithAsyncBlock export const buildThreadsafeFunctionFromFunction = __napiModule.exports.buildThreadsafeFunctionFromFunction export const buildThreadsafeFunctionFromFunctionCalleeHandle = __napiModule.exports.buildThreadsafeFunctionFromFunctionCalleeHandle export const call0 = __napiModule.exports.call0 diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index eabcb25a..e132e565 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -383,29 +383,30 @@ function __napi_rs_initialize_modules(__napiInstance) { __napiInstance.exports['__napi_register__mutate_typed_array_363']?.() __napiInstance.exports['__napi_register__deref_uint8_array_364']?.() __napiInstance.exports['__napi_register__buffer_pass_through_365']?.() - __napiInstance.exports['__napi_register__array_buffer_pass_through_366']?.() - __napiInstance.exports['__napi_register__accept_slice_367']?.() - __napiInstance.exports['__napi_register__accept_arraybuffer_368']?.() - __napiInstance.exports['__napi_register__create_arraybuffer_369']?.() - __napiInstance.exports['__napi_register__u8_array_to_array_370']?.() - __napiInstance.exports['__napi_register__i8_array_to_array_371']?.() - __napiInstance.exports['__napi_register__u16_array_to_array_372']?.() - __napiInstance.exports['__napi_register__i16_array_to_array_373']?.() - __napiInstance.exports['__napi_register__u32_array_to_array_374']?.() - __napiInstance.exports['__napi_register__i32_array_to_array_375']?.() - __napiInstance.exports['__napi_register__f32_array_to_array_376']?.() - __napiInstance.exports['__napi_register__f64_array_to_array_377']?.() - __napiInstance.exports['__napi_register__u64_array_to_array_378']?.() - __napiInstance.exports['__napi_register__i64_array_to_array_379']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_380']?.() - __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_381']?.() - __napiInstance.exports['__napi_register__AsyncBuffer_impl_382']?.() - __napiInstance.exports['__napi_register__async_reduce_buffer_383']?.() - __napiInstance.exports['__napi_register__async_buffer_to_array_384']?.() - __napiInstance.exports['__napi_register__u_init8_array_from_string_385']?.() - __napiInstance.exports['__napi_register__AsyncReader_impl_386']?.() - __napiInstance.exports['__napi_register__Reader_struct_387']?.() - __napiInstance.exports['__napi_register__Reader_impl_389']?.() + __napiInstance.exports['__napi_register__buffer_with_async_block_366']?.() + __napiInstance.exports['__napi_register__array_buffer_pass_through_367']?.() + __napiInstance.exports['__napi_register__accept_slice_368']?.() + __napiInstance.exports['__napi_register__accept_arraybuffer_369']?.() + __napiInstance.exports['__napi_register__create_arraybuffer_370']?.() + __napiInstance.exports['__napi_register__u8_array_to_array_371']?.() + __napiInstance.exports['__napi_register__i8_array_to_array_372']?.() + __napiInstance.exports['__napi_register__u16_array_to_array_373']?.() + __napiInstance.exports['__napi_register__i16_array_to_array_374']?.() + __napiInstance.exports['__napi_register__u32_array_to_array_375']?.() + __napiInstance.exports['__napi_register__i32_array_to_array_376']?.() + __napiInstance.exports['__napi_register__f32_array_to_array_377']?.() + __napiInstance.exports['__napi_register__f64_array_to_array_378']?.() + __napiInstance.exports['__napi_register__u64_array_to_array_379']?.() + __napiInstance.exports['__napi_register__i64_array_to_array_380']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_381']?.() + __napiInstance.exports['__napi_register__accept_uint8_clamped_slice_and_buffer_slice_382']?.() + __napiInstance.exports['__napi_register__AsyncBuffer_impl_383']?.() + __napiInstance.exports['__napi_register__async_reduce_buffer_384']?.() + __napiInstance.exports['__napi_register__async_buffer_to_array_385']?.() + __napiInstance.exports['__napi_register__u_init8_array_from_string_386']?.() + __napiInstance.exports['__napi_register__AsyncReader_impl_387']?.() + __napiInstance.exports['__napi_register__Reader_struct_388']?.() + __napiInstance.exports['__napi_register__Reader_impl_390']?.() } module.exports.Animal = __napiModule.exports.Animal module.exports.AnimalWithDefaultConstructor = __napiModule.exports.AnimalWithDefaultConstructor @@ -477,6 +478,7 @@ module.exports.bigintGetU64AsString = __napiModule.exports.bigintGetU64AsString module.exports.btreeSetToJs = __napiModule.exports.btreeSetToJs module.exports.btreeSetToRust = __napiModule.exports.btreeSetToRust module.exports.bufferPassThrough = __napiModule.exports.bufferPassThrough +module.exports.bufferWithAsyncBlock = __napiModule.exports.bufferWithAsyncBlock module.exports.buildThreadsafeFunctionFromFunction = __napiModule.exports.buildThreadsafeFunctionFromFunction module.exports.buildThreadsafeFunctionFromFunctionCalleeHandle = __napiModule.exports.buildThreadsafeFunctionFromFunctionCalleeHandle module.exports.call0 = __napiModule.exports.call0 diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index 8eb015ab..3b23ac1e 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -434,6 +434,7 @@ module.exports.bigintGetU64AsString = nativeBinding.bigintGetU64AsString module.exports.btreeSetToJs = nativeBinding.btreeSetToJs module.exports.btreeSetToRust = nativeBinding.btreeSetToRust module.exports.bufferPassThrough = nativeBinding.bufferPassThrough +module.exports.bufferWithAsyncBlock = nativeBinding.bufferWithAsyncBlock module.exports.buildThreadsafeFunctionFromFunction = nativeBinding.buildThreadsafeFunctionFromFunction module.exports.buildThreadsafeFunctionFromFunctionCalleeHandle = nativeBinding.buildThreadsafeFunctionFromFunctionCalleeHandle module.exports.call0 = nativeBinding.call0 diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index da567b11..68158323 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -325,6 +325,8 @@ export declare function btreeSetToRust(set: Set): void export declare function bufferPassThrough(buf: Buffer): Promise +export declare function bufferWithAsyncBlock(buf: Buffer): Promise + export declare function buildThreadsafeFunctionFromFunction(callback: (arg0: number, arg1: number) => number): void export declare function buildThreadsafeFunctionFromFunctionCalleeHandle(callback: () => void): void diff --git a/examples/napi/src/typed_array.rs b/examples/napi/src/typed_array.rs index 2ccb2e3c..8aef5de1 100644 --- a/examples/napi/src/typed_array.rs +++ b/examples/napi/src/typed_array.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use napi::{bindgen_prelude::*, JsArrayBuffer}; #[napi] @@ -73,6 +75,16 @@ async fn buffer_pass_through(buf: Buffer) -> Result { Ok(buf) } +#[napi] +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); + }) + .build(env) +} + #[napi] async fn array_buffer_pass_through(buf: Uint8Array) -> Result { Ok(buf)