diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index 2c63c527..99390a10 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -577,6 +577,13 @@ Generated by [AVA](https://avajs.dev). optionalStringField?: string␊ }␊ ␊ + export declare function defineClass(): typeof DynamicRustClass␊ + ␊ + class DynamicRustClass {␊ + constructor(value: number)␊ + rustMethod(): number␊ + }␊ + ␊ export declare function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number␊ ␊ export declare function either3(input: string | number | boolean): number␊ diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index 4e9b2858..b1e8a99d 100644 Binary files a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap differ diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 75dc2efd..6b0669f1 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -262,6 +262,7 @@ import { promiseRawReturnClassInstance, ClassReturnInPromise, acceptUntypedTypedArray, + defineClass, } from '../index.cjs' // import other stuff in `#[napi(module_exports)]` import nativeAddon from '../index.cjs' @@ -581,6 +582,12 @@ test('struct with js_name and methods only (no constructor)', (t) => { ) }) +test('define class', (t) => { + const DynamicRustClass = defineClass() + const instance = new DynamicRustClass(42) + t.is(instance.rustMethod(), 42) +}) + test('async self in class', async (t) => { const b = new Bird('foo') t.is(await b.getNameAsync(), 'foo') diff --git a/examples/napi/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index eea1c018..a5e3b467 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -206,6 +206,7 @@ export const customStatusCode = __napiModule.exports.customStatusCode export const CustomStringEnum = __napiModule.exports.CustomStringEnum export const dateToNumber = __napiModule.exports.dateToNumber export const DEFAULT_COST = __napiModule.exports.DEFAULT_COST +export const defineClass = __napiModule.exports.defineClass export const derefUint8Array = __napiModule.exports.derefUint8Array export const either3 = __napiModule.exports.either3 export const either4 = __napiModule.exports.either4 diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index f572dc17..b9dedfdf 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -65,6 +65,29 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule worker.onmessage = ({ data }) => { __wasmCreateOnMessageForFsProxy(__nodeFs)(data) } + + // The main thread of Node.js waits for all the active handles before exiting. + // But Rust threads are never waited without `thread::join`. + // So here we hack the code of Node.js to prevent the workers from being referenced (active). + // According to https://github.com/nodejs/node/blob/19e0d472728c79d418b74bddff588bea70a403d0/lib/internal/worker.js#L415, + // a worker is consist of two handles: kPublicPort and kHandle. + { + const kPublicPort = Object.getOwnPropertySymbols(worker).find(s => + s.toString().includes("kPublicPort") + ); + if (kPublicPort) { + worker[kPublicPort].ref = () => {}; + } + + const kHandle = Object.getOwnPropertySymbols(worker).find(s => + s.toString().includes("kHandle") + ); + if (kPublicPort) { + worker[kHandle].ref = () => {}; + } + + worker.unref(); + } return worker }, overwriteImports(importObject) { @@ -230,6 +253,7 @@ module.exports.customStatusCode = __napiModule.exports.customStatusCode module.exports.CustomStringEnum = __napiModule.exports.CustomStringEnum module.exports.dateToNumber = __napiModule.exports.dateToNumber module.exports.DEFAULT_COST = __napiModule.exports.DEFAULT_COST +module.exports.defineClass = __napiModule.exports.defineClass module.exports.derefUint8Array = __napiModule.exports.derefUint8Array module.exports.either3 = __napiModule.exports.either3 module.exports.either4 = __napiModule.exports.either4 diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index 044f4c79..7b4fda0f 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -521,6 +521,7 @@ module.exports.customStatusCode = nativeBinding.customStatusCode module.exports.CustomStringEnum = nativeBinding.CustomStringEnum module.exports.dateToNumber = nativeBinding.dateToNumber module.exports.DEFAULT_COST = nativeBinding.DEFAULT_COST +module.exports.defineClass = nativeBinding.defineClass module.exports.derefUint8Array = nativeBinding.derefUint8Array module.exports.either3 = nativeBinding.either3 module.exports.either4 = nativeBinding.either4 diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index ff4186a4..36a1bafd 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -539,6 +539,13 @@ export interface DefaultUseNullableStruct { optionalStringField?: string } +export declare function defineClass(): typeof DynamicRustClass + +class DynamicRustClass { + constructor(value: number) + rustMethod(): number +} + export declare function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number export declare function either3(input: string | number | boolean): number diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index 1d7bc29e..8bad11d5 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -1,7 +1,7 @@ use napi::{ bindgen_prelude::{ - Buffer, ClassInstance, JavaScriptClassExt, JsObjectValue, JsValue, ObjectFinalize, This, - Uint8Array, Unknown, + Buffer, ClassInstance, Function, JavaScriptClassExt, JsObjectValue, JsValue, ObjectFinalize, + This, Uint8Array, Unknown, }, Env, Property, PropertyAttributes, Result, }; @@ -561,3 +561,30 @@ impl ThingList { Thing } } + +#[napi( + ts_return_type = r#"typeof DynamicRustClass\n\nclass DynamicRustClass { + constructor(value: number) + rustMethod(): number +}"# +)] +pub fn define_class(env: &Env) -> Result { + env.define_class( + "DynamicRustClass", + rust_class_constructor_c_callback, + &[Property::new() + .with_utf8_name("rustMethod")? + .with_method(rust_class_method_c_callback)], + ) +} + +#[napi(no_export)] +fn rust_class_constructor(value: i32, mut this: This) -> Result<()> { + this.set_named_property("dynamicValue", value)?; + Ok(()) +} + +#[napi(no_export)] +fn rust_class_method(this: This) -> Result { + this.get_named_property_unchecked::("dynamicValue") +}