diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index fceb34b4..422e049d 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: ['10', '12', '14', '15']
+ node: ['12', '14', '16']
os: [ubuntu-latest, macos-latest, windows-latest]
name: stable - ${{ matrix.os }} - node@${{ matrix.node }}
diff --git a/README.md b/README.md
index a7a3af3d..6c33b702 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
> This project was initialized from [xray](https://github.com/atom/xray)
-A minimal library for building compiled `NodeJS` add-ons in `Rust`.
+A minimal library for building compiled `Node.js` add-ons in `Rust`.
@@ -22,17 +22,18 @@ A minimal library for building compiled `NodeJS` add-ons in `Rust`.
![Windows i686](https://github.com/napi-rs/napi-rs/workflows/Windows%20i686/badge.svg)
[![FreeBSD](https://api.cirrus-ci.com/github/napi-rs/napi-rs.svg)](https://cirrus-ci.com/github/napi-rs/napi-rs?branch=main)
-## Operating Systems
-
-| Linux | macOS | Windows | FreeBSD |
-| ----- | ----- | ------- | ------- |
-| ✓ | ✓ | ✓ | ✓ |
-
-## Node.js
-
-| Node10 | Node12 | Node14 | Node15 |
-| ------ | ------ | ------ | ------ |
-| ✓ | ✓ | ✓ | ✓ |
+| | node12 | node14 | node16 |
+| --------------------- | ------ | ------ | ------ |
+| Windows x64 | ✓ | ✓ | ✓ |
+| Windows x86 | ✓ | ✓ | ✓ |
+| macOS x64 | ✓ | ✓ | ✓ |
+| macOS aarch64 | ✓ | ✓ | ✓ |
+| Linux x64 gnu | ✓ | ✓ | ✓ |
+| Linux x64 musl | ✓ | ✓ | ✓ |
+| Linux aarch64 gnu | ✓ | ✓ | ✓ |
+| Linux arm gnueabihf | ✓ | ✓ | ✓ |
+| Linux aarch64 android | ✓ | ✓ | ✓ |
+| FreeBSD x64 | ✓ | ✓ | ✓ |
This library depends on N-API and requires `Node@10.0.0` or later.
@@ -47,7 +48,7 @@ One nice feature is that this crate allows you to build add-ons purely with the
### Define JavaScript functions
```rust
-#[js_function(1)] // ------> arguments length, omit for zero
+#[js_function(1)] // ------> arguments length
fn fibonacci(ctx: CallContext) -> Result {
let n = ctx.get::(0)?.try_into()?;
ctx.env.create_int64(fibonacci_native(n))
@@ -108,11 +109,11 @@ name = "awesome"
crate-type = ["cdylib"]
[dependencies]
-napi = "1.0"
-napi-derive = "1.0"
+napi = "1"
+napi-derive = "1"
[build-dependencies]
-napi-build = "1.0"
+napi-build = "1"
```
And create `build.rs` in your own project:
@@ -209,6 +210,9 @@ yarn test
| [napi_create_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_create_string_latin1) | 1 | v8.0.0 | ✅ |
| [napi_create_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf16) | 1 | v8.0.0 | ✅ |
| [napi_create_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf8) | 1 | v8.0.0 | ✅ |
+| [napi_type_tag](https://nodejs.org/api/n-api.html#n_api_napi_type_tag) | 8 | v14.8.0, v12.19.0 | ⚠️ |
+
+> I have no plan to implement `nape_type_tag` and related API in `napi-rs`, because we have implemented a `rust` replacement in [TaggedObject](https://github.com/napi-rs/napi-rs/blob/main/napi/src/js_values/tagged_object.rs) which is more convenient and more compatible.
### [Functions to convert from N-API to C types](https://nodejs.org/api/n-api.html#n_api_functions_to_convert_from_n_api_to_c_types)
@@ -258,3 +262,5 @@ yarn test
| [napi_strict_equals](https://nodejs.org/api/n-api.html#n_api_napi_strict_equals) | 1 | v8.0.0 | ✅ |
| [napi_detach_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_detach_arraybuffer) | 7 | v13.3.0 | ✅ |
| [napi_is_detached_arraybuffer](https://nodejs.org/api/n-api.html#n_api_napi_is_detached_arraybuffer) | 7 | v13.3.0 | ✅ |
+| [napi_object_freeze](https://nodejs.org/api/n-api.html#n_api_napi_object_freeze) | 8 | v14.14.0, v12.20.0 | ✅ |
+| [napi_object_seal](https://nodejs.org/api/n-api.html#n_api_napi_object_seal) | 8 | v14.14.0, v12.20.0 | ✅ |
diff --git a/napi/Cargo.toml b/napi/Cargo.toml
index 08a7d43f..eace90e7 100644
--- a/napi/Cargo.toml
+++ b/napi/Cargo.toml
@@ -19,6 +19,7 @@ napi4 = ["napi3", "napi-sys/napi4"]
napi5 = ["napi4", "napi-sys/napi5"]
napi6 = ["napi5", "napi-sys/napi6"]
napi7 = ["napi6", "napi-sys/napi7"]
+napi8 = ["napi7", "napi-sys/napi8"]
serde-json = ["serde", "serde_json"]
tokio_rt = ["futures", "tokio", "once_cell", "napi4"]
diff --git a/napi/src/async_cleanup_hook.rs b/napi/src/async_cleanup_hook.rs
new file mode 100644
index 00000000..481ceab9
--- /dev/null
+++ b/napi/src/async_cleanup_hook.rs
@@ -0,0 +1,28 @@
+use std::mem;
+
+use crate::{sys, Status};
+
+/// Notice
+/// The hook will be removed if `AsyncCleanupHook` was `dropped`.
+/// If you want keep the hook until node process exited, call the `AsyncCleanupHook::forget`.
+#[repr(transparent)]
+pub struct AsyncCleanupHook(pub(crate) sys::napi_async_cleanup_hook_handle);
+
+impl AsyncCleanupHook {
+ /// Safe to forget it.
+ /// Things will be cleanup before process exited.
+ pub fn forget(self) {
+ mem::forget(self);
+ }
+}
+
+impl Drop for AsyncCleanupHook {
+ fn drop(&mut self) {
+ let status = unsafe { sys::napi_remove_async_cleanup_hook(self.0) };
+ assert!(
+ status == sys::Status::napi_ok,
+ "Delete async cleanup hook failed: {}",
+ Status::from(status)
+ );
+ }
+}
diff --git a/napi/src/env.rs b/napi/src/env.rs
index e3969ba2..31bc3347 100644
--- a/napi/src/env.rs
+++ b/napi/src/env.rs
@@ -14,8 +14,10 @@ use crate::{
Error, ExtendedErrorInfo, NodeVersion, Result, Status,
};
+#[cfg(feature = "napi8")]
+use crate::async_cleanup_hook::AsyncCleanupHook;
#[cfg(feature = "napi3")]
-use super::cleanup_env::{CleanupEnvHook, CleanupEnvHookData};
+use crate::cleanup_env::{CleanupEnvHook, CleanupEnvHookData};
#[cfg(all(feature = "serde-json"))]
use crate::js_values::{De, Ser};
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
@@ -1191,6 +1193,58 @@ impl Env {
}
}
+ #[cfg(feature = "napi8")]
+ /// Registers hook, which is a function of type `FnOnce(Arg)`, as a function to be run with the `arg` parameter once the current Node.js environment exits.
+ ///
+ /// Unlike [`add_env_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_env_cleanup_hook), the hook is allowed to be asynchronous.
+ ///
+ /// Otherwise, behavior generally matches that of [`add_env_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_env_cleanup_hook).
+ pub fn add_removable_async_cleanup_hook(
+ &self,
+ arg: Arg,
+ cleanup_fn: F,
+ ) -> Result
+ where
+ F: FnOnce(Arg),
+ Arg: 'static,
+ {
+ let mut handle = ptr::null_mut();
+ check_status!(unsafe {
+ sys::napi_add_async_cleanup_hook(
+ self.0,
+ Some(
+ async_finalize::
+ as unsafe extern "C" fn(handle: sys::napi_async_cleanup_hook_handle, data: *mut c_void),
+ ),
+ Box::leak(Box::new((arg, cleanup_fn))) as *mut (Arg, F) as *mut c_void,
+ &mut handle,
+ )
+ })?;
+ Ok(AsyncCleanupHook(handle))
+ }
+
+ #[cfg(feature = "napi8")]
+ /// This API is very similar to [`add_removable_async_cleanup_hook`](https://docs.rs/napi/latest/napi/struct.Env.html#method.add_removable_async_cleanup_hook)
+ ///
+ /// Use this one if you don't want remove the cleanup hook anymore.
+ pub fn add_async_cleanup_hook(&self, arg: Arg, cleanup_fn: F) -> Result<()>
+ where
+ F: FnOnce(Arg),
+ Arg: 'static,
+ {
+ check_status!(unsafe {
+ sys::napi_add_async_cleanup_hook(
+ self.0,
+ Some(
+ async_finalize::
+ as unsafe extern "C" fn(handle: sys::napi_async_cleanup_hook_handle, data: *mut c_void),
+ ),
+ Box::leak(Box::new((arg, cleanup_fn))) as *mut (Arg, F) as *mut c_void,
+ ptr::null_mut(),
+ )
+ })
+ }
+
/// # Serialize `Rust Struct` into `JavaScript Value`
///
/// ```
@@ -1341,3 +1395,22 @@ unsafe extern "C" fn raw_finalize_with_custom_callback(
let (hint, callback) = *Box::from_raw(finalize_hint as *mut (Hint, Finalize));
callback(hint, Env::from_raw(env));
}
+
+#[cfg(feature = "napi8")]
+unsafe extern "C" fn async_finalize(
+ handle: sys::napi_async_cleanup_hook_handle,
+ data: *mut c_void,
+) where
+ Arg: 'static,
+ F: FnOnce(Arg),
+{
+ let (arg, callback) = *Box::from_raw(data as *mut (Arg, F));
+ callback(arg);
+ if !handle.is_null() {
+ let status = sys::napi_remove_async_cleanup_hook(handle);
+ assert!(
+ status == sys::Status::napi_ok,
+ "Remove async cleanup hook failed after async cleanup callback"
+ );
+ }
+}
diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs
index 07cf9251..6df707ed 100644
--- a/napi/src/js_values/mod.rs
+++ b/napi/src/js_values/mod.rs
@@ -239,6 +239,18 @@ macro_rules! impl_js_value_methods {
})?;
Ok(result)
}
+
+ #[cfg(feature = "napi8")]
+ #[inline]
+ pub fn freeze(&mut self) -> Result<()> {
+ check_status!(unsafe { sys::napi_object_freeze(self.0.env, self.0.value) })
+ }
+
+ #[cfg(feature = "napi8")]
+ #[inline]
+ pub fn seal(&mut self) -> Result<()> {
+ check_status!(unsafe { sys::napi_object_seal(self.0.env, self.0.value) })
+ }
}
};
}
diff --git a/napi/src/lib.rs b/napi/src/lib.rs
index 9bc0cc84..654859f5 100644
--- a/napi/src/lib.rs
+++ b/napi/src/lib.rs
@@ -6,7 +6,7 @@
//!
//! ## Feature flags
//!
-//! ### napi1 ~ napi7
+//! ### napi1 ~ napi8
//!
//! Because `NodeJS` N-API has versions. So there are feature flags to choose what version of `N-API` you want to build for.
//! For example, if you want build a library which can be used by `node@10.17.0`, you should choose the `napi5` or lower.
@@ -75,6 +75,10 @@
//! ```
//!
+#[cfg(feature = "napi8")]
+mod async_cleanup_hook;
+#[cfg(feature = "napi8")]
+pub use async_cleanup_hook::AsyncCleanupHook;
mod async_work;
mod call_context;
#[cfg(feature = "napi3")]
diff --git a/napi/src/status.rs b/napi/src/status.rs
index d5560954..32d8c3f5 100644
--- a/napi/src/status.rs
+++ b/napi/src/status.rs
@@ -29,7 +29,7 @@ pub enum Status {
ArrayBufferExpected,
DetachableArraybufferExpected,
WouldDeadlock,
- Unknown = 1024, // unknown status. for example, using napi3 module in napi7 NodeJS, and generate an invalid napi3 status
+ Unknown = 1024, // unknown status. for example, using napi3 module in napi7 Node.js, and generate an invalid napi3 status
}
impl Display for Status {
diff --git a/sys/Cargo.toml b/sys/Cargo.toml
index 95705f92..9d7e22de 100644
--- a/sys/Cargo.toml
+++ b/sys/Cargo.toml
@@ -18,3 +18,4 @@ napi4 = ["napi3"]
napi5 = ["napi4"]
napi6 = ["napi5"]
napi7 = ["napi6"]
+napi8 = ["napi7"]
diff --git a/sys/src/lib.rs b/sys/src/lib.rs
index c04452a9..27a4e698 100644
--- a/sys/src/lib.rs
+++ b/sys/src/lib.rs
@@ -181,6 +181,18 @@ pub enum napi_key_conversion {
napi_key_numbers_to_strings,
}
+#[cfg(feature = "napi8")]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct napi_async_cleanup_hook_handle__ {
+ _unused: [u8; 0],
+}
+#[cfg(feature = "napi8")]
+pub type napi_async_cleanup_hook_handle = *mut napi_async_cleanup_hook_handle__;
+#[cfg(feature = "napi8")]
+pub type napi_async_cleanup_hook =
+ Option;
+
extern "C" {
pub fn napi_get_last_error_info(
env: napi_env,
@@ -801,6 +813,25 @@ extern "C" {
result: *mut bool,
) -> napi_status;
}
+
+#[cfg(feature = "napi8")]
+extern "C" {
+ pub fn napi_add_async_cleanup_hook(
+ env: napi_env,
+ hook: napi_async_cleanup_hook,
+ arg: *mut c_void,
+ remove_handle: *mut napi_async_cleanup_hook_handle,
+ ) -> napi_status;
+
+ pub fn napi_remove_async_cleanup_hook(
+ remove_handle: napi_async_cleanup_hook_handle,
+ ) -> napi_status;
+
+ pub fn napi_object_freeze(env: napi_env, object: napi_value) -> napi_status;
+
+ pub fn napi_object_seal(env: napi_env, object: napi_value) -> napi_status;
+}
+
#[repr(C)]
#[derive(Copy, Clone)]
pub struct napi_callback_scope__ {
diff --git a/test_module/Cargo.toml b/test_module/Cargo.toml
index 59889e7c..4bd5ab0e 100644
--- a/test_module/Cargo.toml
+++ b/test_module/Cargo.toml
@@ -8,7 +8,7 @@ version = "0.1.0"
crate-type = ["cdylib"]
[features]
-latest = ["napi/napi7"]
+latest = ["napi/napi8"]
napi3 = ["napi/napi3"]
[dependencies]
diff --git a/test_module/__test__/napi7/arraybuffer.spec.ts b/test_module/__test__/napi7/arraybuffer.spec.ts
new file mode 100644
index 00000000..08994f06
--- /dev/null
+++ b/test_module/__test__/napi7/arraybuffer.spec.ts
@@ -0,0 +1,33 @@
+import ava from 'ava'
+
+import { napiVersion } from '../napi-version'
+
+const bindings = require('../../index.node')
+
+const test = napiVersion >= 7 ? ava : ava.skip
+
+test('should be able to detach ArrayBuffer', (t) => {
+ const buf = Buffer.from('hello world')
+ const ab = buf.buffer.slice(0, buf.length)
+ try {
+ bindings.testDetachArrayBuffer(ab)
+ t.is(ab.byteLength, 0)
+ } catch (e) {
+ t.is(e.code, 'DetachableArraybufferExpected')
+ }
+})
+
+test('is detached arraybuffer should work fine', (t) => {
+ const buf = Buffer.from('hello world')
+ const ab = buf.buffer.slice(0, buf.length)
+ try {
+ bindings.testDetachArrayBuffer(ab)
+ const nonDetachedArrayBuffer = new ArrayBuffer(10)
+ const detachedArrayBuffer = new ArrayBuffer(0)
+ t.true(bindings.testIsDetachedArrayBuffer(ab))
+ t.false(bindings.testIsDetachedArrayBuffer(nonDetachedArrayBuffer))
+ t.true(bindings.testIsDetachedArrayBuffer(detachedArrayBuffer))
+ } catch (e) {
+ t.is(e.code, 'DetachableArraybufferExpected')
+ }
+})
diff --git a/test_module/__test__/napi8/async-cleanup.spec.ts b/test_module/__test__/napi8/async-cleanup.spec.ts
new file mode 100644
index 00000000..cd0c80e5
--- /dev/null
+++ b/test_module/__test__/napi8/async-cleanup.spec.ts
@@ -0,0 +1,28 @@
+import { execSync } from 'child_process'
+import { join } from 'path'
+
+import ava from 'ava'
+
+import { napiVersion } from '../napi-version'
+
+const bindings = require('../../index.node')
+
+const test = napiVersion >= 8 ? ava : ava.skip
+
+test('should be able to add async cleanup hook', (t) => {
+ const output = execSync(
+ `node ${join(__dirname, 'sub-process.js')}`,
+ ).toString()
+ t.is(output.trim(), 'Exit from sub process')
+})
+
+test('should be able to add removable async cleanup hook', (t) => {
+ const output = execSync(
+ `node ${join(__dirname, 'sub-process-removable.js')}`,
+ ).toString()
+ t.is(output.trim(), 'Exit from sub process')
+})
+
+test('should be able to remove cleanup hook after added', (t) => {
+ t.notThrows(() => bindings.testRemoveAsyncCleanupHook())
+})
diff --git a/test_module/__test__/napi8/object.spec.ts b/test_module/__test__/napi8/object.spec.ts
new file mode 100644
index 00000000..699d65aa
--- /dev/null
+++ b/test_module/__test__/napi8/object.spec.ts
@@ -0,0 +1,25 @@
+import ava from 'ava'
+
+import { napiVersion } from '../napi-version'
+
+const bindings = require('../../index.node')
+
+const test = napiVersion >= 8 ? ava : ava.skip
+
+test('should be able to freeze object', (t) => {
+ const obj: any = {}
+ bindings.testFreezeObject(obj)
+ t.true(Object.isFrozen(obj))
+ t.throws(() => {
+ obj.a = 1
+ })
+})
+
+test('should be able to seal object', (t) => {
+ const obj: any = {}
+ bindings.testSealObject(obj)
+ t.true(Object.isSealed(obj))
+ t.throws(() => {
+ obj.a = 1
+ })
+})
diff --git a/test_module/__test__/napi8/sub-process-removable.js b/test_module/__test__/napi8/sub-process-removable.js
new file mode 100644
index 00000000..a41fd8bd
--- /dev/null
+++ b/test_module/__test__/napi8/sub-process-removable.js
@@ -0,0 +1,3 @@
+const bindings = require('../../index.node')
+
+bindings.testAddRemovableAsyncCleanupHook()
diff --git a/test_module/__test__/napi8/sub-process.js b/test_module/__test__/napi8/sub-process.js
new file mode 100644
index 00000000..bef22ed4
--- /dev/null
+++ b/test_module/__test__/napi8/sub-process.js
@@ -0,0 +1,3 @@
+const bindings = require('../../index.node')
+
+bindings.testAddAsyncCleanupHook()
diff --git a/test_module/src/lib.rs b/test_module/src/lib.rs
index a5791b25..0749e63f 100644
--- a/test_module/src/lib.rs
+++ b/test_module/src/lib.rs
@@ -13,6 +13,10 @@ mod napi5;
#[cfg(feature = "latest")]
mod napi6;
#[cfg(feature = "latest")]
+mod napi7;
+#[cfg(feature = "latest")]
+mod napi8;
+#[cfg(feature = "latest")]
mod tokio_rt;
mod array;
@@ -61,5 +65,9 @@ fn init(mut exports: JsObject, env: Env) -> Result<()> {
napi5::register_js(&mut exports)?;
#[cfg(feature = "latest")]
napi6::register_js(&mut exports)?;
+ #[cfg(feature = "latest")]
+ napi7::register_js(&mut exports)?;
+ #[cfg(feature = "latest")]
+ napi8::register_js(&mut exports)?;
Ok(())
}
diff --git a/test_module/src/napi7/buffer.rs b/test_module/src/napi7/buffer.rs
new file mode 100644
index 00000000..2283015b
--- /dev/null
+++ b/test_module/src/napi7/buffer.rs
@@ -0,0 +1,14 @@
+use napi::*;
+
+#[js_function(1)]
+pub fn detach_arraybuffer(ctx: CallContext) -> Result {
+ let input = ctx.get::(0)?;
+ input.detach()?;
+ ctx.env.get_undefined()
+}
+
+#[js_function(1)]
+pub fn is_detach_arraybuffer(ctx: CallContext) -> Result {
+ let input = ctx.get::(0)?;
+ ctx.env.get_boolean(input.is_detached()?)
+}
diff --git a/test_module/src/napi7/mod.rs b/test_module/src/napi7/mod.rs
new file mode 100644
index 00000000..46eb4f0d
--- /dev/null
+++ b/test_module/src/napi7/mod.rs
@@ -0,0 +1,11 @@
+use napi::{JsObject, Result};
+
+mod buffer;
+
+use buffer::*;
+
+pub fn register_js(exports: &mut JsObject) -> Result<()> {
+ exports.create_named_method("testDetachArrayBuffer", detach_arraybuffer)?;
+ exports.create_named_method("testIsDetachedArrayBuffer", is_detach_arraybuffer)?;
+ Ok(())
+}
diff --git a/test_module/src/napi8/async_cleanup.rs b/test_module/src/napi8/async_cleanup.rs
new file mode 100644
index 00000000..e2a1ed8e
--- /dev/null
+++ b/test_module/src/napi8/async_cleanup.rs
@@ -0,0 +1,30 @@
+use napi::*;
+
+#[js_function]
+pub fn add_removable_async_cleanup_hook(ctx: CallContext) -> Result {
+ let cleanup_hook = ctx
+ .env
+ .add_removable_async_cleanup_hook(1u32, |_arg: u32| {
+ println!("Exit from sub process");
+ })?;
+ cleanup_hook.forget();
+ ctx.env.get_undefined()
+}
+
+#[js_function]
+pub fn add_async_cleanup_hook(ctx: CallContext) -> Result {
+ ctx.env.add_async_cleanup_hook(1u32, |_arg: u32| {
+ println!("Exit from sub process");
+ })?;
+ ctx.env.get_undefined()
+}
+
+#[js_function]
+pub fn remove_async_cleanup_hook(ctx: CallContext) -> Result {
+ ctx
+ .env
+ .add_removable_async_cleanup_hook(1u32, |_arg: u32| {
+ println!("Exit from sub process");
+ })?;
+ ctx.env.get_undefined()
+}
diff --git a/test_module/src/napi8/mod.rs b/test_module/src/napi8/mod.rs
new file mode 100644
index 00000000..1a36b4c0
--- /dev/null
+++ b/test_module/src/napi8/mod.rs
@@ -0,0 +1,19 @@
+use napi::{JsObject, Result};
+
+mod async_cleanup;
+mod object;
+
+use async_cleanup::*;
+use object::*;
+
+pub fn register_js(exports: &mut JsObject) -> Result<()> {
+ exports.create_named_method("testSealObject", seal_object)?;
+ exports.create_named_method("testFreezeObject", freeze_object)?;
+ exports.create_named_method(
+ "testAddRemovableAsyncCleanupHook",
+ add_removable_async_cleanup_hook,
+ )?;
+ exports.create_named_method("testRemoveAsyncCleanupHook", remove_async_cleanup_hook)?;
+ exports.create_named_method("testAddAsyncCleanupHook", add_async_cleanup_hook)?;
+ Ok(())
+}
diff --git a/test_module/src/napi8/object.rs b/test_module/src/napi8/object.rs
new file mode 100644
index 00000000..d0b67bbd
--- /dev/null
+++ b/test_module/src/napi8/object.rs
@@ -0,0 +1,15 @@
+use napi::*;
+
+#[js_function(1)]
+pub fn seal_object(ctx: CallContext) -> Result {
+ let mut obj: JsObject = ctx.get(0)?;
+ obj.seal()?;
+ ctx.env.get_undefined()
+}
+
+#[js_function(1)]
+pub fn freeze_object(ctx: CallContext) -> Result {
+ let mut obj: JsObject = ctx.get(0)?;
+ obj.freeze()?;
+ ctx.env.get_undefined()
+}