diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 2070fc7f..b907775e 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -80,6 +80,8 @@ pub struct NapiStruct { pub fields: Vec, pub is_tuple: bool, pub kind: NapiStructKind, + pub object_from_js: bool, + pub object_to_js: bool, pub js_mod: Option, pub comments: Vec, pub implement_iterator: bool, diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index a4895680..fa9a150a 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -558,6 +558,46 @@ impl NapiStruct { } }; + let to_napi_value = if self.object_to_js { + quote! { + impl napi::bindgen_prelude::ToNapiValue for #name { + unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result { + let env_wrapper = napi::bindgen_prelude::Env::from(env); + let mut obj = env_wrapper.create_object()?; + + let #destructed_fields = val; + #(#obj_field_setters)* + + napi::bindgen_prelude::Object::to_napi_value(env, obj) + } + } + } + } else { + quote! {} + }; + + let from_napi_value = if self.object_from_js { + quote! { + impl napi::bindgen_prelude::FromNapiValue for #name { + unsafe fn from_napi_value( + env: napi::bindgen_prelude::sys::napi_env, + napi_val: napi::bindgen_prelude::sys::napi_value + ) -> napi::bindgen_prelude::Result { + let env_wrapper = napi::bindgen_prelude::Env::from(env); + let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?; + + #(#obj_field_getters)* + + let val = #destructed_fields; + + Ok(val) + } + } + } + } else { + quote! {} + }; + quote! { impl napi::bindgen_prelude::TypeName for #name { fn type_name() -> &'static str { @@ -569,33 +609,9 @@ impl NapiStruct { } } - impl napi::bindgen_prelude::ToNapiValue for #name { - unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result { - let env_wrapper = napi::bindgen_prelude::Env::from(env); - let mut obj = env_wrapper.create_object()?; + #to_napi_value - let #destructed_fields = val; - #(#obj_field_setters)* - - napi::bindgen_prelude::Object::to_napi_value(env, obj) - } - } - - impl napi::bindgen_prelude::FromNapiValue for #name { - unsafe fn from_napi_value( - env: napi::bindgen_prelude::sys::napi_env, - napi_val: napi::bindgen_prelude::sys::napi_value - ) -> napi::bindgen_prelude::Result { - let env_wrapper = napi::bindgen_prelude::Env::from(env); - let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?; - - #(#obj_field_getters)* - - let val = #destructed_fields; - - Ok(val) - } - } + #from_napi_value impl napi::bindgen_prelude::ValidateNapiValue for #name {} } diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index c9c6fd04..a0fcdd94 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -56,6 +56,8 @@ macro_rules! attrgen { (strict, Strict(Span)), (return_if_invalid, ReturnIfInvalid(Span)), (object, Object(Span)), + (object_from_js, ObjectFromJs(Span, Option)), + (object_to_js, ObjectToJs(Span, Option)), (custom_finalize, CustomFinalize(Span)), (namespace, Namespace(Span, String, Span)), (iterator, Iterator(Span)), diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index cb659923..f4b1a2b6 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -995,6 +995,8 @@ impl ConvertToAST for syn::ItemStruct { fields, is_tuple, kind: struct_kind, + object_from_js: opts.object_from_js(), + object_to_js: opts.object_to_js(), js_mod: namespace, comments: extract_doc_comments(&self.attrs), implement_iterator, diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 7e9a0429..e16b013e 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -172,6 +172,11 @@ Generated by [AVA](https://avajs.dev). }␊ export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number }␊ export function getterFromObj(): number␊ + export interface ObjectOnlyFromJs {␊ + count: number␊ + callback: (err: Error | null, value: number) => any␊ + }␊ + export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void␊ export function asyncPlus100(p: Promise): Promise␊ /** This is an interface for package.json */␊ export interface PackageJson {␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index fc83e0b5..1cd4acce 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 87ad5b3b..818a06c2 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -89,6 +89,7 @@ import { returnJsFunction, testSerdeRoundtrip, createObjWithProperty, + receiveObjectOnlyFromJs, dateToNumber, chronoDateToMillis, derefUint8Array, @@ -816,6 +817,22 @@ Napi4Test('accept ThreadsafeFunction Fatal', async (t) => { }) }) +Napi4Test('object only from js', (t) => { + return new Promise((resolve, reject) => { + receiveObjectOnlyFromJs({ + count: 100, + callback: (err: Error | null, count: number) => { + if (err) { + reject(err) + } else { + t.is(count, 100) + resolve() + } + }, + }) + }) +}) + const Napi5Test = Number(process.versions.napi) >= 5 ? test : test.skip Napi5Test('Date test', (t) => { diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index df0dcced..5b2f39c9 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -162,6 +162,11 @@ export interface TsTypeChanged { } export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number } export function getterFromObj(): number +export interface ObjectOnlyFromJs { + count: number + callback: (err: Error | null, value: number) => any +} +export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void export function asyncPlus100(p: Promise): Promise /** This is an interface for package.json */ export interface PackageJson { diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index 56c81b9e..63ee457f 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -1,4 +1,7 @@ -use napi::{bindgen_prelude::*, JsGlobal, JsNull, JsObject, JsUndefined, Property}; +use napi::{ + bindgen_prelude::*, threadsafe_function::ThreadsafeFunction, JsGlobal, JsNull, JsObject, + JsUndefined, Property, +}; #[napi] fn list_obj_keys(obj: Object) -> Vec { @@ -101,3 +104,23 @@ pub fn create_obj_with_property(env: Env) -> Result { fn getter_from_obj() -> u32 { 42 } + +#[napi(object, object_to_js = false)] +struct ObjectOnlyFromJs { + pub count: u32, + pub callback: ThreadsafeFunction, +} + +#[napi] +fn receive_object_only_from_js( + #[napi(ts_arg_type = "{ count: number, callback: (err: Error | null, count: number) => void }")] + obj: ObjectOnlyFromJs, +) { + let ObjectOnlyFromJs { callback, count } = obj; + std::thread::spawn(move || { + callback.call( + Ok(count), + napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, + ); + }); +}