From 2018cd6fa93174023388043f872c8c457a15d9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 8 Mar 2026 13:14:52 +0100 Subject: [PATCH 1/2] Add experimental Node-API feature infrastructure Vendor Node-API headers from the Node.js repository (replacing the node-api-headers npm package) and introduce infrastructure for experimental feature support. - Vendor headers from Node.js src/ into include/, including experimental API declarations behind #ifdef NAPI_EXPERIMENTAL - Add scripts/update-headers.mjs to download headers and generate layered .def files via clang AST dump - Add add_node_api_cts_experimental_addon() CMake function that defines NAPI_EXPERIMENTAL for addons using experimental APIs - Add implementor feature declaration (features.js) with globalThis.experimentalFeatures for conditional test execution - Add harness test validating the experimentalFeatures global, ensuring every expected feature is declared as a boolean The .def files are deduplicated layers: js_native_api.def contains stable engine-agnostic symbols, node_api.def adds stable runtime symbols, and the _experimental variants add only experimental symbols. CMake combines the layers when generating MSVC import libraries. Closes #26 --- CMakeLists.txt | 34 +- PORTING.md | 184 +++--- implementors/node/features.js | 9 + implementors/node/tests.ts | 8 + include/def/js_native_api.def | 123 ++++ include/def/js_native_api_experimental.def | 5 + include/def/node_api.def | 32 ++ include/def/node_api_experimental.def | 1 + include/js_native_api.h | 624 +++++++++++++++++++++ include/js_native_api_types.h | 240 ++++++++ include/node_api.h | 265 +++++++++ include/node_api_types.h | 58 ++ package-lock.json | 10 +- package.json | 6 +- scripts/update-headers.mjs | 153 +++++ tests/harness/features.js | 23 + 16 files changed, 1689 insertions(+), 86 deletions(-) create mode 100644 implementors/node/features.js create mode 100644 include/def/js_native_api.def create mode 100644 include/def/js_native_api_experimental.def create mode 100644 include/def/node_api.def create mode 100644 include/def/node_api_experimental.def create mode 100644 include/js_native_api.h create mode 100644 include/js_native_api_types.h create mode 100644 include/node_api.h create mode 100644 include/node_api_types.h create mode 100644 scripts/update-headers.mjs create mode 100644 tests/harness/features.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b812d1..32578ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,27 @@ cmake_minimum_required(VERSION 3.15...3.31) project(node-api-cts) -set(NODE_API_HEADERS_DIR ${PROJECT_SOURCE_DIR}/node_modules/node-api-headers) - -if(NOT EXISTS ${NODE_API_HEADERS_DIR}) - message(FATAL_ERROR "Expected ${NODE_API_HEADERS_DIR} to exist") -endif() +set(NODE_API_HEADERS_DIR ${PROJECT_SOURCE_DIR}/include) if(MSVC) + # Combine layered .def files into merged .def files for import library generation + set(DEF_DIR ${NODE_API_HEADERS_DIR}/def) + set(DEF_HEADER "NAME NODE.EXE\nEXPORTS\n") + + file(READ ${DEF_DIR}/js_native_api.def JS_NATIVE_API_SYMBOLS) + file(READ ${DEF_DIR}/node_api.def NODE_API_SYMBOLS) + file(READ ${DEF_DIR}/js_native_api_experimental.def JS_NATIVE_API_EXP_SYMBOLS) + file(READ ${DEF_DIR}/node_api_experimental.def NODE_API_EXP_SYMBOLS) + + set(NODE_API_DEF ${PROJECT_BINARY_DIR}/node_api.def) + file(WRITE ${NODE_API_DEF} ${DEF_HEADER}${JS_NATIVE_API_SYMBOLS}${NODE_API_SYMBOLS}) set(NODE_API_LIB ${PROJECT_BINARY_DIR}/node.lib) - set(NODE_API_DEF ${NODE_API_HEADERS_DIR}/def/node_api.def) execute_process(COMMAND ${CMAKE_AR} /def:${NODE_API_DEF} /out:${NODE_API_LIB} ${CMAKE_STATIC_LINKER_FLAGS}) + + set(NODE_API_EXPERIMENTAL_DEF ${PROJECT_BINARY_DIR}/node_api_experimental.def) + file(WRITE ${NODE_API_EXPERIMENTAL_DEF} ${DEF_HEADER}${JS_NATIVE_API_SYMBOLS}${NODE_API_SYMBOLS}${JS_NATIVE_API_EXP_SYMBOLS}${NODE_API_EXP_SYMBOLS}) + set(NODE_API_EXPERIMENTAL_LIB ${PROJECT_BINARY_DIR}/node_experimental.lib) + execute_process(COMMAND ${CMAKE_AR} /def:${NODE_API_EXPERIMENTAL_DEF} /out:${NODE_API_EXPERIMENTAL_LIB} ${CMAKE_STATIC_LINKER_FLAGS}) endif() function(add_node_api_cts_addon ADDON_NAME) @@ -27,12 +38,21 @@ function(add_node_api_cts_addon ADDON_NAME) if(APPLE) set_target_properties(${ADDON_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") endif() - target_include_directories(${ADDON_NAME} PRIVATE ${NODE_API_HEADERS_DIR}/include) + target_include_directories(${ADDON_NAME} PRIVATE ${NODE_API_HEADERS_DIR}) target_link_libraries(${ADDON_NAME} PRIVATE ${NODE_API_LIB}) target_compile_features(${ADDON_NAME} PRIVATE cxx_std_17) target_compile_definitions(${ADDON_NAME} PRIVATE ADDON_NAME=${ADDON_NAME}) endfunction() +function(add_node_api_cts_experimental_addon ADDON_NAME) + cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "SOURCES") + add_node_api_cts_addon(${ADDON_NAME} ${ARG_SOURCES}) + target_compile_definitions(${ADDON_NAME} PRIVATE NAPI_EXPERIMENTAL) + if(MSVC) + target_link_libraries(${ADDON_NAME} PRIVATE ${NODE_API_EXPERIMENTAL_LIB}) + endif() +endfunction() + file(GLOB_RECURSE cmake_dirs RELATIVE ${CMAKE_SOURCE_DIR} tests/*/CMakeLists.txt) foreach(cmake_file ${cmake_dirs}) diff --git a/PORTING.md b/PORTING.md index 834d704..475720f 100644 --- a/PORTING.md +++ b/PORTING.md @@ -23,87 +23,136 @@ in `js_native_api.h` and is therefore engine-agnostic. ## Difficulty Ratings Difficulty is assessed on two axes: + - **Size/complexity** — total lines of C/C++ and JS across all source files - **Runtime-API dependence** — pure `js_native_api.h` is cheapest; Node.js extensions and direct libuv calls require harness work or Node-only scoping -| Rating | Meaning | -|---|---| -| Easy | Small test, pure `js_native_api.h` or trivial runtime API, straightforward 1:1 port | -| Medium | Moderate size or uses a Node.js extension API that the harness will need to abstract | -| Hard | Large test and/or deep libuv/worker/SEA dependency; may need new harness primitives or Node-only scoping | +| Rating | Meaning | +| ------ | -------------------------------------------------------------------------------------------------------- | +| Easy | Small test, pure `js_native_api.h` or trivial runtime API, straightforward 1:1 port | +| Medium | Moderate size or uses a Node.js extension API that the harness will need to abstract | +| Hard | Large test and/or deep libuv/worker/SEA dependency; may need new harness primitives or Node-only scoping | ## Engine-specific (`js-native-api`) Tests covering the engine-specific part of Node-API, defined in `js_native_api.h`. -| Directory | Status | Difficulty | -|---|---|---| -| `2_function_arguments` | Ported | — | -| `3_callbacks` | Not ported | Easy | -| `4_object_factory` | Not ported | Easy | -| `5_function_factory` | Not ported | Easy | -| `6_object_wrap` | Not ported | Medium | -| `7_factory_wrap` | Not ported | Easy | -| `8_passing_wrapped` | Not ported | Easy | -| `test_array` | Not ported | Easy | -| `test_bigint` | Not ported | Easy | -| `test_cannot_run_js` | Not ported | Medium | -| `test_constructor` | Not ported | Medium | -| `test_conversions` | Not ported | Medium | -| `test_dataview` | Not ported | Easy | -| `test_date` | Not ported | Easy | -| `test_error` | Not ported | Medium | -| `test_exception` | Not ported | Medium | -| `test_finalizer` | Not ported | Medium | -| `test_function` | Not ported | Medium | -| `test_general` | Not ported | Hard | -| `test_handle_scope` | Not ported | Easy | -| `test_instance_data` | Not ported | Easy | -| `test_new_target` | Not ported | Easy | -| `test_number` | Not ported | Easy | -| `test_object` | Not ported | Hard | -| `test_promise` | Not ported | Easy | -| `test_properties` | Not ported | Easy | -| `test_reference` | Not ported | Medium | -| `test_reference_double_free` | Not ported | Easy | -| `test_sharedarraybuffer` | Not ported | Medium | -| `test_string` | Not ported | Medium | -| `test_symbol` | Not ported | Easy | -| `test_typedarray` | Not ported | Medium | +| Directory | Status | Difficulty | +| ---------------------------- | ---------- | ---------- | +| `2_function_arguments` | Ported | — | +| `3_callbacks` | Not ported | Easy | +| `4_object_factory` | Not ported | Easy | +| `5_function_factory` | Not ported | Easy | +| `6_object_wrap` | Not ported | Medium | +| `7_factory_wrap` | Not ported | Easy | +| `8_passing_wrapped` | Not ported | Easy | +| `test_array` | Not ported | Easy | +| `test_bigint` | Not ported | Easy | +| `test_cannot_run_js` | Not ported | Medium | +| `test_constructor` | Not ported | Medium | +| `test_conversions` | Not ported | Medium | +| `test_dataview` | Not ported | Easy | +| `test_date` | Not ported | Easy | +| `test_error` | Not ported | Medium | +| `test_exception` | Not ported | Medium | +| `test_finalizer` | Not ported | Medium | +| `test_function` | Not ported | Medium | +| `test_general` | Not ported | Hard | +| `test_handle_scope` | Not ported | Easy | +| `test_instance_data` | Not ported | Easy | +| `test_new_target` | Not ported | Easy | +| `test_number` | Not ported | Easy | +| `test_object` | Not ported | Hard | +| `test_promise` | Not ported | Easy | +| `test_properties` | Not ported | Easy | +| `test_reference` | Not ported | Medium | +| `test_reference_double_free` | Not ported | Easy | +| `test_sharedarraybuffer` | Not ported | Medium | +| `test_string` | Not ported | Medium | +| `test_symbol` | Not ported | Easy | +| `test_typedarray` | Not ported | Medium | ## Runtime-specific (`node-api`) Tests covering the runtime-specific part of Node-API, defined in `node_api.h`. -| Directory | Status | Difficulty | -|---|---|---| -| `1_hello_world` | Not ported | Easy | -| `test_async` | Not ported | Hard | -| `test_async_cleanup_hook` | Not ported | Hard | -| `test_async_context` | Not ported | Hard | -| `test_buffer` | Not ported | Medium | -| `test_callback_scope` | Not ported | Hard | -| `test_cleanup_hook` | Not ported | Medium | -| `test_env_teardown_gc` | Not ported | Easy | -| `test_exception` | Not ported | Easy | -| `test_fatal` | Not ported | Hard | -| `test_fatal_exception` | Not ported | Easy | -| `test_general` | Not ported | Medium | -| `test_init_order` | Not ported | Medium | -| `test_instance_data` | Not ported | Hard | -| `test_make_callback` | Not ported | Hard | -| `test_make_callback_recurse` | Not ported | Hard | -| `test_null_init` | Not ported | Medium | -| `test_reference_by_node_api_version` | Not ported | Medium | -| `test_sea_addon` | Not ported | Hard | -| `test_threadsafe_function` | Not ported | Hard | -| `test_threadsafe_function_shutdown` | Not ported | Hard | -| `test_uv_loop` | Not ported | Hard | -| `test_uv_threadpool_size` | Not ported | Hard | -| `test_worker_buffer_callback` | Not ported | Hard | -| `test_worker_terminate` | Not ported | Hard | -| `test_worker_terminate_finalization` | Not ported | Hard | +| Directory | Status | Difficulty | +| ------------------------------------ | ---------- | ---------- | +| `1_hello_world` | Not ported | Easy | +| `test_async` | Not ported | Hard | +| `test_async_cleanup_hook` | Not ported | Hard | +| `test_async_context` | Not ported | Hard | +| `test_buffer` | Not ported | Medium | +| `test_callback_scope` | Not ported | Hard | +| `test_cleanup_hook` | Not ported | Medium | +| `test_env_teardown_gc` | Not ported | Easy | +| `test_exception` | Not ported | Easy | +| `test_fatal` | Not ported | Hard | +| `test_fatal_exception` | Not ported | Easy | +| `test_general` | Not ported | Medium | +| `test_init_order` | Not ported | Medium | +| `test_instance_data` | Not ported | Hard | +| `test_make_callback` | Not ported | Hard | +| `test_make_callback_recurse` | Not ported | Hard | +| `test_null_init` | Not ported | Medium | +| `test_reference_by_node_api_version` | Not ported | Medium | +| `test_sea_addon` | Not ported | Hard | +| `test_threadsafe_function` | Not ported | Hard | +| `test_threadsafe_function_shutdown` | Not ported | Hard | +| `test_uv_loop` | Not ported | Hard | +| `test_uv_threadpool_size` | Not ported | Hard | +| `test_worker_buffer_callback` | Not ported | Hard | +| `test_worker_terminate` | Not ported | Hard | +| `test_worker_terminate_finalization` | Not ported | Hard | + +## Experimental Node-API Features + +Several tests in the upstream Node.js repository use experimental APIs that are guarded behind +`#ifdef NAPI_EXPERIMENTAL` in Node.js's `js_native_api.h`. + +When `NAPI_EXPERIMENTAL` is defined in Node.js, `NAPI_VERSION` is set to +`NAPI_VERSION_EXPERIMENTAL (2147483647)`. The `NAPI_MODULE` macro exports this version, and the +runtime uses it to decide whether to enable experimental behavior for that addon. + +| Feature macro | APIs | Used by | +| --------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------- | +| `NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER` | `node_api_is_sharedarraybuffer`, `node_api_create_sharedarraybuffer` | `test_dataview`, `test_sharedarraybuffer` | +| `NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES` | `node_api_create_object_with_properties` | `test_object` | +| `NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE` | `node_api_set_prototype` | `test_general` | +| `NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER` | `node_api_post_finalizer` | `test_general`, `test_finalizer`, `6_object_wrap` | + +Tests that depend on these APIs are currently ported without the experimental test cases (marked +as "Partial" in the status column) or not ported at all. + +### Infrastructure for experimental features + +The CTS provides the following infrastructure (see [#26](https://github.com/nodejs/node-api-cts/issues/26)): + +1. **Vendored headers** — The `include/` directory contains Node-API headers vendored directly from + the Node.js repository (updated via `npm run update-headers`). These include experimental API + declarations behind `#ifdef NAPI_EXPERIMENTAL`. + +2. **CMake build function** — `add_node_api_cts_experimental_addon()` compiles an addon with + `NAPI_EXPERIMENTAL` defined, enabling all experimental API declarations and setting + `NAPI_VERSION` to `NAPI_VERSION_EXPERIMENTAL` (2147483647). + +3. **Implementor feature declaration** — Each runtime provides a `features.js` harness module that + sets `globalThis.experimentalFeatures` to an object declaring which experimental features it + supports (e.g., `{ sharedArrayBuffer: true, postFinalizer: true }`). + +4. **Conditional test execution** — JS test files guard experimental assertions behind feature + checks. When a feature is unsupported, the guarded code silently does not execute. + + ```js + if (experimentalFeatures.sharedArrayBuffer) { + const addon = loadAddon("test_sharedarraybuffer"); + // ... assertions + } + ``` + + The `loadAddon` call must be inside the guard because addons linked against experimental + symbols will fail to load on runtimes that don't export those symbols. ## Special Considerations @@ -144,6 +193,7 @@ The following tests call into libuv directly — `napi_get_uv_event_loop`, `uv_t - `test_uv_loop`, `test_uv_threadpool_size` Porting options: + 1. **Node-only scope** — mark these tests as Node.js-only and skip on other runtimes. 2. **Harness abstraction** — introduce a minimal platform-agnostic threading/async API in the harness (e.g., `cts_thread_create`, `cts_async_schedule`) that implementors back with their diff --git a/implementors/node/features.js b/implementors/node/features.js new file mode 100644 index 0000000..d06bad9 --- /dev/null +++ b/implementors/node/features.js @@ -0,0 +1,9 @@ +// Declares which experimental Node-API features this runtime supports. +// Each key corresponds to a NODE_API_EXPERIMENTAL_HAS_* compile-time macro. +// Other implementors should set unsupported features to false or omit them. +globalThis.experimentalFeatures = { + sharedArrayBuffer: true, + createObjectWithProperties: true, + setPrototype: true, + postFinalizer: true, +}; diff --git a/implementors/node/tests.ts b/implementors/node/tests.ts index d39c64b..a8e9689 100644 --- a/implementors/node/tests.ts +++ b/implementors/node/tests.ts @@ -10,6 +10,12 @@ assert( const ROOT_PATH = path.resolve(import.meta.dirname, "..", ".."); const TESTS_ROOT_PATH = path.join(ROOT_PATH, "tests"); +const FEATURES_MODULE_PATH = path.join( + ROOT_PATH, + "implementors", + "node", + "features.js" +); const ASSERT_MODULE_PATH = path.join( ROOT_PATH, "implementors", @@ -59,6 +65,8 @@ export function runFileInSubprocess( // Using file scheme prefix when to enable imports on Windows "--expose-gc", "--import", + "file://" + FEATURES_MODULE_PATH, + "--import", "file://" + ASSERT_MODULE_PATH, "--import", "file://" + LOAD_ADDON_MODULE_PATH, diff --git a/include/def/js_native_api.def b/include/def/js_native_api.def new file mode 100644 index 0000000..a32e819 --- /dev/null +++ b/include/def/js_native_api.def @@ -0,0 +1,123 @@ +napi_add_finalizer +napi_adjust_external_memory +napi_call_function +napi_check_object_type_tag +napi_close_escapable_handle_scope +napi_close_handle_scope +napi_coerce_to_bool +napi_coerce_to_number +napi_coerce_to_object +napi_coerce_to_string +napi_create_array +napi_create_array_with_length +napi_create_arraybuffer +napi_create_bigint_int64 +napi_create_bigint_uint64 +napi_create_bigint_words +napi_create_dataview +napi_create_date +napi_create_double +napi_create_error +napi_create_external +napi_create_external_arraybuffer +napi_create_function +napi_create_int32 +napi_create_int64 +napi_create_object +napi_create_promise +napi_create_range_error +napi_create_reference +napi_create_string_latin1 +napi_create_string_utf16 +napi_create_string_utf8 +napi_create_symbol +napi_create_type_error +napi_create_typedarray +napi_create_uint32 +napi_define_class +napi_define_properties +napi_delete_element +napi_delete_property +napi_delete_reference +napi_detach_arraybuffer +napi_escape_handle +napi_get_all_property_names +napi_get_and_clear_last_exception +napi_get_array_length +napi_get_arraybuffer_info +napi_get_boolean +napi_get_cb_info +napi_get_dataview_info +napi_get_date_value +napi_get_element +napi_get_global +napi_get_instance_data +napi_get_last_error_info +napi_get_named_property +napi_get_new_target +napi_get_null +napi_get_property +napi_get_property_names +napi_get_prototype +napi_get_reference_value +napi_get_typedarray_info +napi_get_undefined +napi_get_value_bigint_int64 +napi_get_value_bigint_uint64 +napi_get_value_bigint_words +napi_get_value_bool +napi_get_value_double +napi_get_value_external +napi_get_value_int32 +napi_get_value_int64 +napi_get_value_string_latin1 +napi_get_value_string_utf16 +napi_get_value_string_utf8 +napi_get_value_uint32 +napi_get_version +napi_has_element +napi_has_named_property +napi_has_own_property +napi_has_property +napi_instanceof +napi_is_array +napi_is_arraybuffer +napi_is_dataview +napi_is_date +napi_is_detached_arraybuffer +napi_is_error +napi_is_exception_pending +napi_is_promise +napi_is_typedarray +napi_new_instance +napi_object_freeze +napi_object_seal +napi_open_escapable_handle_scope +napi_open_handle_scope +napi_reference_ref +napi_reference_unref +napi_reject_deferred +napi_remove_wrap +napi_resolve_deferred +napi_run_script +napi_set_element +napi_set_instance_data +napi_set_named_property +napi_set_property +napi_strict_equals +napi_throw +napi_throw_error +napi_throw_range_error +napi_throw_type_error +napi_type_tag_object +napi_typeof +napi_unwrap +napi_wrap +node_api_create_external_string_latin1 +node_api_create_external_string_utf16 +node_api_create_property_key_latin1 +node_api_create_property_key_utf16 +node_api_create_property_key_utf8 +node_api_create_syntax_error +node_api_symbol_for +node_api_throw_syntax_error diff --git a/include/def/js_native_api_experimental.def b/include/def/js_native_api_experimental.def new file mode 100644 index 0000000..2a8d679 --- /dev/null +++ b/include/def/js_native_api_experimental.def @@ -0,0 +1,5 @@ +node_api_create_object_with_properties +node_api_create_sharedarraybuffer +node_api_is_sharedarraybuffer +node_api_post_finalizer +node_api_set_prototype diff --git a/include/def/node_api.def b/include/def/node_api.def new file mode 100644 index 0000000..61ca273 --- /dev/null +++ b/include/def/node_api.def @@ -0,0 +1,32 @@ +napi_acquire_threadsafe_function +napi_add_async_cleanup_hook +napi_add_env_cleanup_hook +napi_async_destroy +napi_async_init +napi_call_threadsafe_function +napi_cancel_async_work +napi_close_callback_scope +napi_create_async_work +napi_create_buffer +napi_create_buffer_copy +napi_create_external_buffer +napi_create_threadsafe_function +napi_delete_async_work +napi_fatal_error +napi_fatal_exception +napi_get_buffer_info +napi_get_node_version +napi_get_threadsafe_function_context +napi_get_uv_event_loop +napi_is_buffer +napi_make_callback +napi_module_register +napi_open_callback_scope +napi_queue_async_work +napi_ref_threadsafe_function +napi_release_threadsafe_function +napi_remove_async_cleanup_hook +napi_remove_env_cleanup_hook +napi_unref_threadsafe_function +node_api_create_buffer_from_arraybuffer +node_api_get_module_file_name diff --git a/include/def/node_api_experimental.def b/include/def/node_api_experimental.def new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/include/def/node_api_experimental.def @@ -0,0 +1 @@ + diff --git a/include/js_native_api.h b/include/js_native_api.h new file mode 100644 index 0000000..31eee12 --- /dev/null +++ b/include/js_native_api.h @@ -0,0 +1,624 @@ +#ifndef SRC_JS_NATIVE_API_H_ +#define SRC_JS_NATIVE_API_H_ + +// This file needs to be compatible with C compilers. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +#include "js_native_api_types.h" + +// If you need __declspec(dllimport), either include instead, or +// define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. +#ifndef NAPI_EXTERN +#ifdef _WIN32 +#define NAPI_EXTERN __declspec(dllexport) +#elif defined(__wasm__) +#define NAPI_EXTERN \ + __attribute__((visibility("default"))) \ + __attribute__((__import_module__("napi"))) +#else +#define NAPI_EXTERN __attribute__((visibility("default"))) +#endif +#endif + +#define NAPI_AUTO_LENGTH SIZE_MAX + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END +#endif + +EXTERN_C_START + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info( + node_api_basic_env env, const napi_extended_error_info** result); + +// Getters for defined singletons +NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_null(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_global(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env, + bool value, + napi_value* result); + +// Methods to create Primitive types/Objects +NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env, + napi_value* result); +#ifdef NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_object_with_properties(napi_env env, + napi_value prototype_or_null, + napi_value* property_names, + napi_value* property_values, + size_t property_count, + napi_value* result); +#endif // NAPI_EXPERIMENTAL + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_array_with_length(napi_env env, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_double(napi_env env, + double value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_int32(napi_env env, + int32_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_uint32(napi_env env, + uint32_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_latin1( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf8(napi_env env, + const char* str, + size_t length, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env, + const char16_t* str, + size_t length, + napi_value* result); +#if NAPI_VERSION >= 10 +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_latin1( + napi_env env, + char* str, + size_t length, + node_api_basic_finalize finalize_callback, + void* finalize_hint, + napi_value* result, + bool* copied); +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_external_string_utf16(napi_env env, + char16_t* str, + size_t length, + node_api_basic_finalize finalize_callback, + void* finalize_hint, + napi_value* result, + bool* copied); + +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_latin1( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf8( + napi_env env, const char* str, size_t length, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf16( + napi_env env, const char16_t* str, size_t length, napi_value* result); +#endif // NAPI_VERSION >= 10 + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env, + napi_value description, + napi_value* result); +#if NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL +node_api_symbol_for(napi_env env, + const char* utf8description, + size_t length, + napi_value* result); +#endif // NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL napi_create_function(napi_env env, + const char* utf8name, + size_t length, + napi_callback cb, + void* data, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_type_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_range_error(napi_env env, + napi_value code, + napi_value msg, + napi_value* result); +#if NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_syntax_error( + napi_env env, napi_value code, napi_value msg, napi_value* result); +#endif // NAPI_VERSION >= 9 + +// Methods to get the native napi_value from Primitive type +NAPI_EXTERN napi_status NAPI_CDECL napi_typeof(napi_env env, + napi_value value, + napi_valuetype* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_double(napi_env env, + napi_value value, + double* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int32(napi_env env, + napi_value value, + int32_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_uint32(napi_env env, + napi_value value, + uint32_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int64(napi_env env, + napi_value value, + int64_t* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bool(napi_env env, + napi_value value, + bool* result); + +// Copies LATIN-1 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_latin1( + napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); + +// Copies UTF-8 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf8( + napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); + +// Copies UTF-16 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf16(napi_env env, + napi_value value, + char16_t* buf, + size_t bufsize, + size_t* result); + +// Methods to coerce values +// These APIs may execute user scripts +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_bool(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_number(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_object(napi_env env, + napi_value value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_string(napi_env env, + napi_value value, + napi_value* result); + +// Methods to work with Objects +#ifdef NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE +NAPI_EXTERN napi_status NAPI_CDECL node_api_set_prototype(napi_env env, + napi_value object, + napi_value value); +#endif +NAPI_EXTERN napi_status NAPI_CDECL napi_get_prototype(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_property_names(napi_env env, + napi_value object, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_property(napi_env env, + napi_value object, + napi_value key, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_property(napi_env env, + napi_value object, + napi_value key, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_own_property(napi_env env, + napi_value object, + napi_value key, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_named_property(napi_env env, + napi_value object, + const char* utf8name, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_named_property(napi_env env, + napi_value object, + const char* utf8name, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_set_element(napi_env env, + napi_value object, + uint32_t index, + napi_value value); +NAPI_EXTERN napi_status NAPI_CDECL napi_has_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_element(napi_env env, + napi_value object, + uint32_t index, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_define_properties(napi_env env, + napi_value object, + size_t property_count, + const napi_property_descriptor* properties); + +// Methods to work with Arrays +NAPI_EXTERN napi_status NAPI_CDECL napi_is_array(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_array_length(napi_env env, + napi_value value, + uint32_t* result); + +// Methods to compare values +NAPI_EXTERN napi_status NAPI_CDECL napi_strict_equals(napi_env env, + napi_value lhs, + napi_value rhs, + bool* result); + +// Methods to work with Functions +NAPI_EXTERN napi_status NAPI_CDECL napi_call_function(napi_env env, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_new_instance(napi_env env, + napi_value constructor, + size_t argc, + const napi_value* argv, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env, + napi_value object, + napi_value constructor, + bool* result); + +// Methods to work with napi_callbacks + +// Gets all callback info in a single call. (Ugly, but faster.) +NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info( + napi_env env, // [in] Node-API environment handle + napi_callback_info cbinfo, // [in] Opaque callback-info handle + size_t* argc, // [in-out] Specifies the size of the provided argv array + // and receives the actual count of args. + napi_value* argv, // [out] Array of values + napi_value* this_arg, // [out] Receives the JS 'this' arg for the call + void** data); // [out] Receives the data pointer for the callback. + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_new_target( + napi_env env, napi_callback_info cbinfo, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_define_class(napi_env env, + const char* utf8name, + size_t length, + napi_callback constructor, + void* data, + size_t property_count, + const napi_property_descriptor* properties, + napi_value* result); + +// Methods to work with external data objects +NAPI_EXTERN napi_status NAPI_CDECL +napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env, + napi_value js_object, + void** result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external(napi_env env, + void* data, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, + napi_value value, + void** result); + +// Methods to control object lifespan + +// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_reference(napi_env env, + napi_value value, + uint32_t initial_refcount, + napi_ref* result); + +// Deletes a reference. The referenced value is released, and may +// be GC'd unless there are other references to it. +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_reference(node_api_basic_env env, + napi_ref ref); + +// Increments the reference count, optionally returning the resulting count. +// After this call the reference will be a strong reference because its +// refcount is >0, and the referenced object is effectively "pinned". +// Calling this when the refcount is 0 and the object is unavailable +// results in an error. +NAPI_EXTERN napi_status NAPI_CDECL napi_reference_ref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Decrements the reference count, optionally returning the resulting count. +// If the result is 0 the reference is now weak and the object may be GC'd +// at any time if there are no other references. Calling this when the +// refcount is already 0 results in an error. +NAPI_EXTERN napi_status NAPI_CDECL napi_reference_unref(napi_env env, + napi_ref ref, + uint32_t* result); + +// Attempts to get a referenced value. If the reference is weak, +// the value might no longer be available, in that case the call +// is still successful but the result is NULL. +NAPI_EXTERN napi_status NAPI_CDECL napi_get_reference_value(napi_env env, + napi_ref ref, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_open_handle_scope(napi_env env, napi_handle_scope* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_close_handle_scope(napi_env env, napi_handle_scope scope); +NAPI_EXTERN napi_status NAPI_CDECL napi_open_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_close_escapable_handle_scope( + napi_env env, napi_escapable_handle_scope scope); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_escape_handle(napi_env env, + napi_escapable_handle_scope scope, + napi_value escapee, + napi_value* result); + +// Methods to support error handling +NAPI_EXTERN napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_type_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status NAPI_CDECL napi_throw_range_error(napi_env env, + const char* code, + const char* msg); +#if NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL node_api_throw_syntax_error(napi_env env, + const char* code, + const char* msg); +#endif // NAPI_VERSION >= 9 +NAPI_EXTERN napi_status NAPI_CDECL napi_is_error(napi_env env, + napi_value value, + bool* result); + +// Methods to support catching exceptions +NAPI_EXTERN napi_status NAPI_CDECL napi_is_exception_pending(napi_env env, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_and_clear_last_exception(napi_env env, napi_value* result); + +// Methods to work with array buffers and typed arrays +NAPI_EXTERN napi_status NAPI_CDECL napi_is_arraybuffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_create_arraybuffer(napi_env env, + size_t byte_length, + void** data, + napi_value* result); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external_arraybuffer(napi_env env, + void* external_data, + size_t byte_length, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info( + napi_env env, napi_value arraybuffer, void** data, size_t* byte_length); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_typedarray(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_typedarray(napi_env env, + napi_typedarray_type type, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_typedarray_info(napi_env env, + napi_value typedarray, + napi_typedarray_type* type, + size_t* length, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_dataview(napi_env env, + size_t length, + napi_value arraybuffer, + size_t byte_offset, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_dataview(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_dataview_info(napi_env env, + napi_value dataview, + size_t* bytelength, + void** data, + napi_value* arraybuffer, + size_t* byte_offset); + +#ifdef NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +NAPI_EXTERN napi_status NAPI_CDECL +node_api_is_sharedarraybuffer(napi_env env, napi_value value, bool* result); +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_sharedarraybuffer( + napi_env env, size_t byte_length, void** data, napi_value* result); +#endif // NAPI_EXPERIMENTAL + +// version management +NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_basic_env env, + uint32_t* result); + +// Promises +NAPI_EXTERN napi_status NAPI_CDECL napi_create_promise(napi_env env, + napi_deferred* deferred, + napi_value* promise); +NAPI_EXTERN napi_status NAPI_CDECL napi_resolve_deferred(napi_env env, + napi_deferred deferred, + napi_value resolution); +NAPI_EXTERN napi_status NAPI_CDECL napi_reject_deferred(napi_env env, + napi_deferred deferred, + napi_value rejection); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_promise(napi_env env, + napi_value value, + bool* is_promise); + +// Running a script +NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, + napi_value script, + napi_value* result); + +// Memory management +NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( + node_api_basic_env env, int64_t change_in_bytes, int64_t* adjusted_value); + +#if NAPI_VERSION >= 5 + +// Dates +NAPI_EXTERN napi_status NAPI_CDECL napi_create_date(napi_env env, + double time, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_is_date(napi_env env, + napi_value value, + bool* is_date); + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env, + napi_value value, + double* result); + +// Add finalizer for pointer +NAPI_EXTERN napi_status NAPI_CDECL +napi_add_finalizer(napi_env env, + napi_value js_object, + void* finalize_data, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); + +#endif // NAPI_VERSION >= 5 + +#ifdef NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_post_finalizer(node_api_basic_env env, + napi_finalize finalize_cb, + void* finalize_data, + void* finalize_hint); + +#endif // NAPI_EXPERIMENTAL + +#if NAPI_VERSION >= 6 + +// BigInt +NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_bigint_words(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t* result, + bool* lossless); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_uint64( + napi_env env, napi_value value, uint64_t* result, bool* lossless); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_value_bigint_words(napi_env env, + napi_value value, + int* sign_bit, + size_t* word_count, + uint64_t* words); + +// Object +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_all_property_names(napi_env env, + napi_value object, + napi_key_collection_mode key_mode, + napi_key_filter key_filter, + napi_key_conversion key_conversion, + napi_value* result); + +// Instance data +NAPI_EXTERN napi_status NAPI_CDECL +napi_set_instance_data(node_api_basic_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_instance_data(node_api_basic_env env, void** data); +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 7 +// ArrayBuffer detaching +NAPI_EXTERN napi_status NAPI_CDECL +napi_detach_arraybuffer(napi_env env, napi_value arraybuffer); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_is_detached_arraybuffer(napi_env env, napi_value value, bool* result); +#endif // NAPI_VERSION >= 7 + +#if NAPI_VERSION >= 8 +// Type tagging +NAPI_EXTERN napi_status NAPI_CDECL napi_type_tag_object( + napi_env env, napi_value value, const napi_type_tag* type_tag); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_check_object_type_tag(napi_env env, + napi_value value, + const napi_type_tag* type_tag, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_object_freeze(napi_env env, + napi_value object); +NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env, + napi_value object); +#endif // NAPI_VERSION >= 8 + +EXTERN_C_END + +#endif // SRC_JS_NATIVE_API_H_ diff --git a/include/js_native_api_types.h b/include/js_native_api_types.h new file mode 100644 index 0000000..add2088 --- /dev/null +++ b/include/js_native_api_types.h @@ -0,0 +1,240 @@ +#ifndef SRC_JS_NATIVE_API_TYPES_H_ +#define SRC_JS_NATIVE_API_TYPES_H_ + +// Use INT_MAX, this should only be consumed by the pre-processor anyway. +#define NAPI_VERSION_EXPERIMENTAL 2147483647 +#ifndef NAPI_VERSION +#ifdef NAPI_EXPERIMENTAL +#define NAPI_VERSION NAPI_VERSION_EXPERIMENTAL +#else +// The baseline version for Node-API. +// NAPI_VERSION controls which version is used by default when compiling +// a native addon. If the addon developer wants to use functions from a +// newer Node-API version not yet available in all LTS versions, they can +// set NAPI_VERSION to explicitly depend on that version. +#define NAPI_VERSION 8 +#endif +#endif + +#if defined(NAPI_EXPERIMENTAL) && \ + !defined(NODE_API_EXPERIMENTAL_NO_WARNING) && \ + !defined(NODE_WANT_INTERNALS) +#ifdef _MSC_VER +#pragma message("NAPI_EXPERIMENTAL is enabled. " \ + "Experimental features may be unstable.") +#else +#warning "NAPI_EXPERIMENTAL is enabled. " \ + "Experimental features may be unstable." +#endif +#endif + +// This file needs to be compatible with C compilers. +// This is a public include file, and these includes have essentially +// become part of its API. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) + +#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) +typedef uint16_t char16_t; +#endif + +#ifndef NAPI_CDECL +#ifdef _WIN32 +#define NAPI_CDECL __cdecl +#else +#define NAPI_CDECL +#endif +#endif + +// JSVM API types are all opaque pointers for ABI stability +// typedef undefined structs instead of void* for compile time type safety +typedef struct napi_env__* napi_env; + +// We need to mark APIs which can be called during garbage collection (GC), +// meaning that they do not affect the state of the JS engine, and can +// therefore be called synchronously from a finalizer that itself runs +// synchronously during GC. Such APIs can receive either a `napi_env` or a +// `node_api_basic_env` as their first parameter, because we should be able to +// also call them during normal, non-garbage-collecting operations, whereas +// APIs that affect the state of the JS engine can only receive a `napi_env` as +// their first parameter, because we must not call them during GC. In lieu of +// inheritance, we use the properties of the const qualifier to accomplish +// this, because both a const and a non-const value can be passed to an API +// expecting a const value, but only a non-const value can be passed to an API +// expecting a non-const value. +// +// In conjunction with appropriate CFLAGS to warn us if we're passing a const +// (basic) environment into an API that expects a non-const environment, and +// the definition of basic finalizer function pointer types below, which +// receive a basic environment as their first parameter, and can thus only call +// basic APIs (unless the user explicitly casts the environment), we achieve +// the ability to ensure at compile time that we do not call APIs that affect +// the state of the JS engine from a synchronous (basic) finalizer. +#if !defined(NAPI_EXPERIMENTAL) || \ + (defined(NAPI_EXPERIMENTAL) && \ + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) +typedef struct napi_env__* node_api_nogc_env; +#else +typedef const struct napi_env__* node_api_nogc_env; +#endif +typedef node_api_nogc_env node_api_basic_env; + +typedef struct napi_value__* napi_value; +typedef struct napi_ref__* napi_ref; +typedef struct napi_handle_scope__* napi_handle_scope; +typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; +typedef struct napi_callback_info__* napi_callback_info; +typedef struct napi_deferred__* napi_deferred; + +typedef enum { + napi_default = 0, + napi_writable = 1 << 0, + napi_enumerable = 1 << 1, + napi_configurable = 1 << 2, + + // Used with napi_define_class to distinguish static properties + // from instance properties. Ignored by napi_define_properties. + napi_static = 1 << 10, + +#if NAPI_VERSION >= 8 + // Default for class methods. + napi_default_method = napi_writable | napi_configurable, + + // Default for object properties, like in JS obj[prop]. + napi_default_jsproperty = napi_writable | napi_enumerable | napi_configurable, +#endif // NAPI_VERSION >= 8 +} napi_property_attributes; + +typedef enum { + // ES6 types (corresponds to typeof) + napi_undefined, + napi_null, + napi_boolean, + napi_number, + napi_string, + napi_symbol, + napi_object, + napi_function, + napi_external, + napi_bigint, +} napi_valuetype; + +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, + napi_bigint64_array, + napi_biguint64_array, +#define NODE_API_HAS_FLOAT16_ARRAY + napi_float16_array, +} napi_typedarray_type; + +typedef enum { + napi_ok, + napi_invalid_arg, + napi_object_expected, + napi_string_expected, + napi_name_expected, + napi_function_expected, + napi_number_expected, + napi_boolean_expected, + napi_array_expected, + napi_generic_failure, + napi_pending_exception, + napi_cancelled, + napi_escape_called_twice, + napi_handle_scope_mismatch, + napi_callback_scope_mismatch, + napi_queue_full, + napi_closing, + napi_bigint_expected, + napi_date_expected, + napi_arraybuffer_expected, + napi_detachable_arraybuffer_expected, + napi_would_deadlock, // unused + napi_no_external_buffers_allowed, + napi_cannot_run_js, +} napi_status; +// Note: when adding a new enum value to `napi_status`, please also update +// * `const int last_status` in the definition of `napi_get_last_error_info()' +// in file js_native_api_v8.cc. +// * `const char* error_messages[]` in file js_native_api_v8.cc with a brief +// message explaining the error. +// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly +// added value(s). + +typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, + napi_callback_info info); +typedef void(NAPI_CDECL* napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); + +#if !defined(NAPI_EXPERIMENTAL) || \ + (defined(NAPI_EXPERIMENTAL) && \ + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) +typedef napi_finalize node_api_nogc_finalize; +#else +typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env, + void* finalize_data, + void* finalize_hint); +#endif +typedef node_api_nogc_finalize node_api_basic_finalize; + +typedef struct { + // One of utf8name or name should be NULL. + const char* utf8name; + napi_value name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; + +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; + +#if NAPI_VERSION >= 6 +typedef enum { + napi_key_include_prototypes, + napi_key_own_only +} napi_key_collection_mode; + +typedef enum { + napi_key_all_properties = 0, + napi_key_writable = 1, + napi_key_enumerable = 1 << 1, + napi_key_configurable = 1 << 2, + napi_key_skip_strings = 1 << 3, + napi_key_skip_symbols = 1 << 4 +} napi_key_filter; + +typedef enum { + napi_key_keep_numbers, + napi_key_numbers_to_strings +} napi_key_conversion; +#endif // NAPI_VERSION >= 6 + +#if NAPI_VERSION >= 8 +typedef struct { + uint64_t lower; + uint64_t upper; +} napi_type_tag; +#endif // NAPI_VERSION >= 8 + +#endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/include/node_api.h b/include/node_api.h new file mode 100644 index 0000000..46dbb02 --- /dev/null +++ b/include/node_api.h @@ -0,0 +1,265 @@ +#ifndef SRC_NODE_API_H_ +#define SRC_NODE_API_H_ + +#if defined(BUILDING_NODE_EXTENSION) && !defined(NAPI_EXTERN) +#ifdef _WIN32 +// Building native addon against node +#define NAPI_EXTERN __declspec(dllimport) +#elif defined(__wasm__) +#define NAPI_EXTERN __attribute__((__import_module__("napi"))) +#endif +#endif +#include "js_native_api.h" +#include "node_api_types.h" + +struct uv_loop_s; // Forward declaration. + +#ifdef _WIN32 +#define NAPI_MODULE_EXPORT __declspec(dllexport) +#else +#ifdef __EMSCRIPTEN__ +#define NAPI_MODULE_EXPORT \ + __attribute__((visibility("default"))) __attribute__((used)) +#else +#define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +#if defined(__GNUC__) +#define NAPI_NO_RETURN __attribute__((noreturn)) +#elif defined(_WIN32) +#define NAPI_NO_RETURN __declspec(noreturn) +#else +#define NAPI_NO_RETURN +#endif + +// Used by deprecated registration method napi_module_register. +typedef struct napi_module { + int nm_version; + unsigned int nm_flags; + const char* nm_filename; + napi_addon_register_func nm_register_func; + const char* nm_modname; + void* nm_priv; + void* reserved[4]; +} napi_module; + +#define NAPI_MODULE_VERSION 1 + +#define NAPI_MODULE_INITIALIZER_X(base, version) \ + NAPI_MODULE_INITIALIZER_X_HELPER(base, version) +#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version + +#ifdef __wasm__ +#define NAPI_MODULE_INITIALIZER_BASE napi_register_wasm_v +#else +#define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v +#endif + +#define NODE_API_MODULE_GET_API_VERSION_BASE node_api_module_get_api_version_v + +#define NAPI_MODULE_INITIALIZER \ + NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, NAPI_MODULE_VERSION) + +#define NODE_API_MODULE_GET_API_VERSION \ + NAPI_MODULE_INITIALIZER_X(NODE_API_MODULE_GET_API_VERSION_BASE, \ + NAPI_MODULE_VERSION) + +#define NAPI_MODULE_INIT() \ + EXTERN_C_START \ + NAPI_MODULE_EXPORT int32_t NODE_API_MODULE_GET_API_VERSION(void) { \ + return NAPI_VERSION; \ + } \ + NAPI_MODULE_EXPORT napi_value NAPI_MODULE_INITIALIZER(napi_env env, \ + napi_value exports); \ + EXTERN_C_END \ + napi_value NAPI_MODULE_INITIALIZER(napi_env env, napi_value exports) + +#define NAPI_MODULE(modname, regfunc) \ + NAPI_MODULE_INIT() { return regfunc(env, exports); } + +// Deprecated. Use NAPI_MODULE. +#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ + NAPI_MODULE(modname, regfunc) + +EXTERN_C_START + +// Deprecated. Replaced by symbol-based registration defined by NAPI_MODULE +// and NAPI_MODULE_INIT macros. +NAPI_EXTERN void NAPI_CDECL +napi_module_register(napi_module* mod); + +NAPI_EXTERN NAPI_NO_RETURN void NAPI_CDECL +napi_fatal_error(const char* location, + size_t location_len, + const char* message, + size_t message_len); + +// Methods for custom handling of async operations +NAPI_EXTERN napi_status NAPI_CDECL +napi_async_init(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_context* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_async_destroy(napi_env env, napi_async_context async_context); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_make_callback(napi_env env, + napi_async_context async_context, + napi_value recv, + napi_value func, + size_t argc, + const napi_value* argv, + napi_value* result); + +// Methods to provide node::Buffer functionality with napi types +NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer(napi_env env, + size_t length, + void** data, + napi_value* result); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_external_buffer(napi_env env, + size_t length, + void* data, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +#if NAPI_VERSION >= 10 + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_create_buffer_from_arraybuffer(napi_env env, + napi_value arraybuffer, + size_t byte_offset, + size_t byte_length, + napi_value* result); +#endif // NAPI_VERSION >= 10 + +NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer_copy(napi_env env, + size_t length, + const void* data, + void** result_data, + napi_value* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_buffer(napi_env env, + napi_value value, + bool* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_buffer_info(napi_env env, + napi_value value, + void** data, + size_t* length); + +// Methods to manage simple async operations +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result); +NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_basic_env env, + napi_async_work work); +NAPI_EXTERN napi_status NAPI_CDECL +napi_cancel_async_work(node_api_basic_env env, napi_async_work work); + +// version management +NAPI_EXTERN napi_status NAPI_CDECL napi_get_node_version( + node_api_basic_env env, const napi_node_version** version); + +#if NAPI_VERSION >= 2 + +// Return the current libuv event loop for a given environment +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_uv_event_loop(node_api_basic_env env, struct uv_loop_s** loop); + +#endif // NAPI_VERSION >= 2 + +#if NAPI_VERSION >= 3 + +NAPI_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env, + napi_value err); + +NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook( + node_api_basic_env env, napi_cleanup_hook fun, void* arg); + +NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook( + node_api_basic_env env, napi_cleanup_hook fun, void* arg); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context context, + napi_callback_scope* result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_close_callback_scope(napi_env env, napi_callback_scope scope); + +#endif // NAPI_VERSION >= 3 + +#if NAPI_VERSION >= 4 + +// Calling into JS from other threads +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_threadsafe_function(napi_env env, + napi_value func, + napi_value async_resource, + napi_value async_resource_name, + size_t max_queue_size, + size_t initial_thread_count, + void* thread_finalize_data, + napi_finalize thread_finalize_cb, + void* context, + napi_threadsafe_function_call_js call_js_cb, + napi_threadsafe_function* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_threadsafe_function_context( + napi_threadsafe_function func, void** result); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_call_threadsafe_function(napi_threadsafe_function func, + void* data, + napi_threadsafe_function_call_mode is_blocking); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_acquire_threadsafe_function(napi_threadsafe_function func); + +NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function( + napi_threadsafe_function func, napi_threadsafe_function_release_mode mode); + +NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function( + node_api_basic_env env, napi_threadsafe_function func); + +NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function( + node_api_basic_env env, napi_threadsafe_function func); + +#endif // NAPI_VERSION >= 4 + +#if NAPI_VERSION >= 8 + +NAPI_EXTERN napi_status NAPI_CDECL +napi_add_async_cleanup_hook(node_api_basic_env env, + napi_async_cleanup_hook hook, + void* arg, + napi_async_cleanup_hook_handle* remove_handle); + +NAPI_EXTERN napi_status NAPI_CDECL +napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle); + +#endif // NAPI_VERSION >= 8 + +#if NAPI_VERSION >= 9 + +NAPI_EXTERN napi_status NAPI_CDECL +node_api_get_module_file_name(node_api_basic_env env, const char** result); + +#endif // NAPI_VERSION >= 9 + +EXTERN_C_END + +#endif // SRC_NODE_API_H_ diff --git a/include/node_api_types.h b/include/node_api_types.h new file mode 100644 index 0000000..79123f0 --- /dev/null +++ b/include/node_api_types.h @@ -0,0 +1,58 @@ +#ifndef SRC_NODE_API_TYPES_H_ +#define SRC_NODE_API_TYPES_H_ + +#include "js_native_api_types.h" + +typedef napi_value(NAPI_CDECL* napi_addon_register_func)(napi_env env, + napi_value exports); +// False positive: https://github.com/cpplint/cpplint/issues/409 +// NOLINTNEXTLINE (readability/casting) +typedef int32_t(NAPI_CDECL* node_api_addon_get_api_version_func)(void); + +typedef struct napi_callback_scope__* napi_callback_scope; +typedef struct napi_async_context__* napi_async_context; +typedef struct napi_async_work__* napi_async_work; + +#if NAPI_VERSION >= 3 +typedef void(NAPI_CDECL* napi_cleanup_hook)(void* arg); +#endif // NAPI_VERSION >= 3 + +#if NAPI_VERSION >= 4 +typedef struct napi_threadsafe_function__* napi_threadsafe_function; +#endif // NAPI_VERSION >= 4 + +#if NAPI_VERSION >= 4 +typedef enum { + napi_tsfn_release, + napi_tsfn_abort +} napi_threadsafe_function_release_mode; + +typedef enum { + napi_tsfn_nonblocking, + napi_tsfn_blocking +} napi_threadsafe_function_call_mode; +#endif // NAPI_VERSION >= 4 + +typedef void(NAPI_CDECL* napi_async_execute_callback)(napi_env env, void* data); +typedef void(NAPI_CDECL* napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); +#if NAPI_VERSION >= 4 +typedef void(NAPI_CDECL* napi_threadsafe_function_call_js)( + napi_env env, napi_value js_callback, void* context, void* data); +#endif // NAPI_VERSION >= 4 + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; + +#if NAPI_VERSION >= 8 +typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle; +typedef void(NAPI_CDECL* napi_async_cleanup_hook)( + napi_async_cleanup_hook_handle handle, void* data); +#endif // NAPI_VERSION >= 8 + +#endif // SRC_NODE_API_TYPES_H_ diff --git a/package-lock.json b/package-lock.json index d8ec84f..8fa1a01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,7 @@ }, "devDependencies": { "@types/node": "^24.10.1", - "eslint": "^9.39.1", - "node-api-headers": "^1.7.0" + "eslint": "^9.39.1" } }, "node_modules/@eslint-community/eslint-utils": { @@ -885,13 +884,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-api-headers": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.7.0.tgz", - "integrity": "sha512-uJMGdkhVwu9+I3UsVvI3KW6ICAy/yDfsu5Br9rSnTtY3WpoaComXvKloiV5wtx0Md2rn0B9n29Ys2WMNwWxj9A==", - "dev": true, - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", diff --git a/package.json b/package.json index 1590d7b..073d41d 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,12 @@ "lint": "eslint", "addons:configure": "cmake -S . -B ./build", "addons:build": "cmake --build ./build", - "addons:clean": "git clean -xf '**/*.node' && rm -rf ./build" + "addons:clean": "git clean -xf '**/*.node' && rm -rf ./build", + "update-headers": "node scripts/update-headers.mjs" }, "devDependencies": { "@types/node": "^24.10.1", - "eslint": "^9.39.1", - "node-api-headers": "^1.7.0" + "eslint": "^9.39.1" }, "dependencies": { "amaro": "^1.1.5" diff --git a/scripts/update-headers.mjs b/scripts/update-headers.mjs new file mode 100644 index 0000000..3f00887 --- /dev/null +++ b/scripts/update-headers.mjs @@ -0,0 +1,153 @@ +#!/usr/bin/env node + +// Downloads Node-API headers from the Node.js repository and generates +// Windows .def files by extracting function declarations via clang AST dump. +// +// Usage: node scripts/update-headers.mjs [--branch ] + +import { execFile } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const ROOT = path.resolve(import.meta.dirname, ".."); +const INCLUDE_DIR = path.join(ROOT, "include"); +const DEF_DIR = path.join(INCLUDE_DIR, "def"); + +const HEADER_FILES = [ + "js_native_api.h", + "js_native_api_types.h", + "node_api.h", + "node_api_types.h", +]; + +function parseBranch() { + const idx = process.argv.indexOf("--branch"); + return idx !== -1 && process.argv[idx + 1] + ? process.argv[idx + 1] + : "main"; +} + +async function downloadHeaders(branch) { + const baseUrl = `https://raw.githubusercontent.com/nodejs/node/${branch}/src`; + const results = await Promise.all( + HEADER_FILES.map(async (file) => { + const url = `${baseUrl}/${file}`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download ${url}: ${response.status}`); + } + return { file, content: await response.text() }; + }) + ); + + fs.mkdirSync(INCLUDE_DIR, { recursive: true }); + for (const { file, content } of results) { + fs.writeFileSync(path.join(INCLUDE_DIR, file), content); + console.log(`Downloaded ${file}`); + } +} + +function detectLatestStableVersion() { + const content = fs.readFileSync( + path.join(INCLUDE_DIR, "js_native_api.h"), + "utf8" + ); + const nodeApiContent = fs.readFileSync( + path.join(INCLUDE_DIR, "node_api.h"), + "utf8" + ); + const combined = content + nodeApiContent; + const versions = [...combined.matchAll(/NAPI_VERSION >= (\d+)/g)].map( + (m) => Number(m[1]) + ); + return Math.max(...versions); +} + +function runClang(headerFile, { experimental = false, napiVersion } = {}) { + const args = [ + ...(experimental ? ["-D", "NAPI_EXPERIMENTAL"] : []), + ...(napiVersion ? ["-D", `NAPI_VERSION=${napiVersion}`] : []), + "-Xclang", + "-ast-dump=json", + "-fsyntax-only", + "-I", + INCLUDE_DIR, + path.join(INCLUDE_DIR, headerFile), + ]; + return new Promise((resolve, reject) => { + execFile( + "clang", + args, + { maxBuffer: 10 * 1024 * 1024 }, + (error, stdout, stderr) => { + if (error) { + reject( + new Error( + `clang failed on ${headerFile}: ${error.message}\n${stderr}` + ) + ); + return; + } + resolve(stdout); + } + ); + }); +} + +function extractSymbols(astJson) { + const ast = JSON.parse(astJson); + return new Set( + ast.inner + .filter((node) => node.kind === "FunctionDecl") + .map((node) => node.name) + ); +} + +function generateDef(symbols) { + return [...symbols].sort().join("\n") + "\n"; +} + +function writeDef(filename, symbols) { + const defPath = path.join(DEF_DIR, filename); + fs.writeFileSync(defPath, generateDef(symbols)); + console.log( + `Generated ${path.relative(ROOT, defPath)} (${symbols.size} symbols)` + ); +} + +async function generateDefFiles() { + const latestVersion = detectLatestStableVersion(); + console.log(`Detected latest stable NAPI_VERSION: ${latestVersion}`); + + const [jsNativeApiAst, nodeApiAst, jsNativeApiExpAst, nodeApiExpAst] = + await Promise.all([ + runClang("js_native_api.h", { napiVersion: latestVersion }), + runClang("node_api.h", { napiVersion: latestVersion }), + runClang("js_native_api.h", { experimental: true }), + runClang("node_api.h", { experimental: true }), + ]); + + const jsNativeApiStable = extractSymbols(jsNativeApiAst); + const allStable = extractSymbols(nodeApiAst); + const jsNativeApiAll = extractSymbols(jsNativeApiExpAst); + const allAll = extractSymbols(nodeApiExpAst); + + // Each .def contains only its own layer (no duplicates across files) + const nodeApiStable = allStable.difference(jsNativeApiStable); + const jsNativeApiExp = jsNativeApiAll.difference(jsNativeApiStable); + const nodeApiExp = allAll.difference(allStable).difference(jsNativeApiExp); + + fs.mkdirSync(DEF_DIR, { recursive: true }); + + writeDef("js_native_api.def", jsNativeApiStable); + writeDef("node_api.def", nodeApiStable); + writeDef("js_native_api_experimental.def", jsNativeApiExp); + writeDef("node_api_experimental.def", nodeApiExp); +} + +const branch = parseBranch(); +console.log(`Downloading headers from nodejs/node@${branch}...`); +await downloadHeaders(branch); +console.log("\nGenerating .def files via clang..."); +await generateDefFiles(); +console.log("\nDone."); diff --git a/tests/harness/features.js b/tests/harness/features.js new file mode 100644 index 0000000..21aac68 --- /dev/null +++ b/tests/harness/features.js @@ -0,0 +1,23 @@ +'use strict'; + +assert.ok( + typeof experimentalFeatures === 'object' && experimentalFeatures !== null, + 'Expected a global experimentalFeatures object' +); + +// Every expected feature must be declared as a boolean (true or false). +// Update this list when new NODE_API_EXPERIMENTAL_HAS_* macros are added. +const expectedFeatures = [ + 'sharedArrayBuffer', + 'createObjectWithProperties', + 'setPrototype', + 'postFinalizer', +]; + +for (const feature of expectedFeatures) { + assert.strictEqual( + typeof experimentalFeatures[feature], + 'boolean', + `Expected experimentalFeatures.${feature} to be a boolean` + ); +} From d57e9e954b187f2ca8e3bd288814f7c05dd64288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sun, 8 Mar 2026 13:45:40 +0100 Subject: [PATCH 2/2] feat: port 6_object_wrap test to CTS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port all three build targets and test files from upstream: - myobject (stable) — object wrap with getters/setters/methods - myobject_basic_finalizer (experimental) — basic finalizer verification - nested_wrap (NAPI_VERSION=10) — nested ref finalization The experimental target (myobject_basic_finalizer) exercises the add_node_api_cts_experimental_addon() CMake function for the first time, serving as end-to-end validation of the experimental infrastructure. Co-Authored-By: Claude Opus 4.6 --- PORTING.md | 2 +- .../6_object_wrap/CMakeLists.txt | 6 + tests/js-native-api/6_object_wrap/myobject.cc | 270 ++++++++++++++++++ tests/js-native-api/6_object_wrap/myobject.h | 28 ++ .../6_object_wrap/nested_wrap.cc | 99 +++++++ .../js-native-api/6_object_wrap/nested_wrap.h | 33 +++ .../6_object_wrap/nested_wrap.js | 15 + .../6_object_wrap/test-basic-finalizer.js | 17 ++ tests/js-native-api/6_object_wrap/test.js | 46 +++ 9 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 tests/js-native-api/6_object_wrap/CMakeLists.txt create mode 100644 tests/js-native-api/6_object_wrap/myobject.cc create mode 100644 tests/js-native-api/6_object_wrap/myobject.h create mode 100644 tests/js-native-api/6_object_wrap/nested_wrap.cc create mode 100644 tests/js-native-api/6_object_wrap/nested_wrap.h create mode 100644 tests/js-native-api/6_object_wrap/nested_wrap.js create mode 100644 tests/js-native-api/6_object_wrap/test-basic-finalizer.js create mode 100644 tests/js-native-api/6_object_wrap/test.js diff --git a/PORTING.md b/PORTING.md index 475720f..4cb66df 100644 --- a/PORTING.md +++ b/PORTING.md @@ -44,7 +44,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h | `3_callbacks` | Not ported | Easy | | `4_object_factory` | Not ported | Easy | | `5_function_factory` | Not ported | Easy | -| `6_object_wrap` | Not ported | Medium | +| `6_object_wrap` | Ported | — | | `7_factory_wrap` | Not ported | Easy | | `8_passing_wrapped` | Not ported | Easy | | `test_array` | Not ported | Easy | diff --git a/tests/js-native-api/6_object_wrap/CMakeLists.txt b/tests/js-native-api/6_object_wrap/CMakeLists.txt new file mode 100644 index 0000000..91ff280 --- /dev/null +++ b/tests/js-native-api/6_object_wrap/CMakeLists.txt @@ -0,0 +1,6 @@ +add_node_api_cts_addon(myobject myobject.cc) + +add_node_api_cts_experimental_addon(myobject_basic_finalizer SOURCES myobject.cc) + +add_node_api_cts_addon(nested_wrap nested_wrap.cc) +target_compile_definitions(nested_wrap PRIVATE NAPI_VERSION=10) diff --git a/tests/js-native-api/6_object_wrap/myobject.cc b/tests/js-native-api/6_object_wrap/myobject.cc new file mode 100644 index 0000000..750d4f4 --- /dev/null +++ b/tests/js-native-api/6_object_wrap/myobject.cc @@ -0,0 +1,270 @@ +#include "myobject.h" +#include "../common.h" +#include "../entry_point.h" +#include "assert.h" + +typedef int32_t FinalizerData; + +napi_ref MyObject::constructor; + +MyObject::MyObject(double value) + : value_(value), env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor(node_api_basic_env env, + void* nativeObject, + void* /*finalize_hint*/) { + MyObject* obj = static_cast(nativeObject); + delete obj; + + FinalizerData* data; + NODE_API_BASIC_CALL_RETURN_VOID( + env, napi_get_instance_data(env, reinterpret_cast(&data))); + *data += 1; +} + +void MyObject::Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + {"value", nullptr, nullptr, GetValue, SetValue, 0, napi_default, 0}, + {"valueReadonly", + nullptr, + nullptr, + GetValue, + nullptr, + 0, + napi_default, + 0}, + DECLARE_NODE_API_PROPERTY("plusOne", PlusOne), + DECLARE_NODE_API_PROPERTY("multiply", Multiply), + }; + + napi_value cons; + NODE_API_CALL_RETURN_VOID( + env, + napi_define_class(env, + "MyObject", + -1, + New, + nullptr, + sizeof(properties) / sizeof(napi_property_descriptor), + properties, + &cons)); + + NODE_API_CALL_RETURN_VOID(env, + napi_create_reference(env, cons, 1, &constructor)); + + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, exports, "MyObject", cons)); +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + napi_value new_target; + NODE_API_CALL(env, napi_get_new_target(env, info, &new_target)); + bool is_constructor = (new_target != nullptr); + + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + if (is_constructor) { + // Invoked as constructor: `new MyObject(...)` + double value = 0; + + napi_valuetype valuetype; + NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype)); + + if (valuetype != napi_undefined) { + NODE_API_CALL(env, napi_get_value_double(env, args[0], &value)); + } + + MyObject* obj = new MyObject(value); + + obj->env_ = env; + NODE_API_CALL(env, + napi_wrap(env, + _this, + obj, + MyObject::Destructor, + nullptr /* finalize_hint */, + &obj->wrapper_)); + + return _this; + } + + // Invoked as plain function `MyObject(...)`, turn into construct call. + argc = 1; + napi_value argv[1] = {args[0]}; + + napi_value cons; + NODE_API_CALL(env, napi_get_reference_value(env, constructor, &cons)); + + napi_value instance; + NODE_API_CALL(env, napi_new_instance(env, cons, argc, argv, &instance)); + + return instance; +} + +napi_value MyObject::GetValue(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + napi_value num; + NODE_API_CALL(env, napi_create_double(env, obj->value_, &num)); + + return num; +} + +napi_value MyObject::SetValue(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + NODE_API_CALL(env, napi_get_value_double(env, args[0], &obj->value_)); + + return nullptr; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_value _this; + NODE_API_CALL(env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + obj->value_ += 1; + + napi_value num; + NODE_API_CALL(env, napi_create_double(env, obj->value_, &num)); + + return num; +} + +napi_value MyObject::Multiply(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value _this; + NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + + double multiple = 1; + if (argc >= 1) { + NODE_API_CALL(env, napi_get_value_double(env, args[0], &multiple)); + } + + MyObject* obj; + NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast(&obj))); + + napi_value cons; + NODE_API_CALL(env, napi_get_reference_value(env, constructor, &cons)); + + const int kArgCount = 1; + napi_value argv[kArgCount]; + NODE_API_CALL(env, napi_create_double(env, obj->value_ * multiple, argv)); + + napi_value instance; + NODE_API_CALL(env, napi_new_instance(env, cons, kArgCount, argv, &instance)); + + return instance; +} + +// This finalizer should never be invoked. +void ObjectWrapDanglingReferenceFinalizer(node_api_basic_env env, + void* finalize_data, + void* finalize_hint) { + assert(0 && "unreachable"); +} + +napi_ref dangling_ref; +napi_value ObjectWrapDanglingReference(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)); + + // Create a napi_wrap and remove it immediately, whilst leaving the out-param + // ref dangling (not deleted). + NODE_API_CALL(env, + napi_wrap(env, + args[0], + nullptr, + ObjectWrapDanglingReferenceFinalizer, + nullptr, + &dangling_ref)); + NODE_API_CALL(env, napi_remove_wrap(env, args[0], nullptr)); + + return args[0]; +} + +napi_value ObjectWrapDanglingReferenceTest(napi_env env, + napi_callback_info info) { + napi_value out; + napi_value ret; + NODE_API_CALL(env, napi_get_reference_value(env, dangling_ref, &out)); + + if (out == nullptr) { + // If the napi_ref has been invalidated, delete it. + NODE_API_CALL(env, napi_delete_reference(env, dangling_ref)); + NODE_API_CALL(env, napi_get_boolean(env, true, &ret)); + } else { + // The dangling napi_ref is still valid. + NODE_API_CALL(env, napi_get_boolean(env, false, &ret)); + } + return ret; +} + +static napi_value GetFinalizerCallCount(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + FinalizerData* data; + napi_value result; + + NODE_API_CALL(env, + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr)); + NODE_API_CALL(env, + napi_get_instance_data(env, reinterpret_cast(&data))); + NODE_API_CALL(env, napi_create_int32(env, *data, &result)); + return result; +} + +static void finalizeData(napi_env env, void* data, void* hint) { + delete reinterpret_cast(data); +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + FinalizerData* data = new FinalizerData; + *data = 0; + NODE_API_CALL(env, napi_set_instance_data(env, data, finalizeData, nullptr)); + + MyObject::Init(env, exports); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("objectWrapDanglingReference", + ObjectWrapDanglingReference), + DECLARE_NODE_API_PROPERTY("objectWrapDanglingReferenceTest", + ObjectWrapDanglingReferenceTest), + DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", GetFinalizerCallCount), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/6_object_wrap/myobject.h b/tests/js-native-api/6_object_wrap/myobject.h new file mode 100644 index 0000000..0faff67 --- /dev/null +++ b/tests/js-native-api/6_object_wrap/myobject.h @@ -0,0 +1,28 @@ +#ifndef TEST_JS_NATIVE_API_6_OBJECT_WRAP_MYOBJECT_H_ +#define TEST_JS_NATIVE_API_6_OBJECT_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static void Init(napi_env env, napi_value exports); + static void Destructor(node_api_basic_env env, + void* nativeObject, + void* finalize_hint); + + private: + explicit MyObject(double value_ = 0); + ~MyObject(); + + static napi_value New(napi_env env, napi_callback_info info); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + static napi_value Multiply(napi_env env, napi_callback_info info); + static napi_ref constructor; + double value_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_JS_NATIVE_API_6_OBJECT_WRAP_MYOBJECT_H_ diff --git a/tests/js-native-api/6_object_wrap/nested_wrap.cc b/tests/js-native-api/6_object_wrap/nested_wrap.cc new file mode 100644 index 0000000..f854a35 --- /dev/null +++ b/tests/js-native-api/6_object_wrap/nested_wrap.cc @@ -0,0 +1,99 @@ +#include "nested_wrap.h" +#include "../common.h" +#include "../entry_point.h" + +napi_ref NestedWrap::constructor{}; +static int finalization_count = 0; + +NestedWrap::NestedWrap() {} + +NestedWrap::~NestedWrap() { + napi_delete_reference(env_, wrapper_); + + // Delete the nested reference as well. + napi_delete_reference(env_, nested_); +} + +void NestedWrap::Destructor(node_api_basic_env env, + void* nativeObject, + void* /*finalize_hint*/) { + // Once this destructor is called, it cancels all pending + // finalizers for the object by deleting the references. + NestedWrap* obj = static_cast(nativeObject); + delete obj; + + finalization_count++; +} + +void NestedWrap::Init(napi_env env, napi_value exports) { + napi_value cons; + NODE_API_CALL_RETURN_VOID( + env, + napi_define_class( + env, "NestedWrap", -1, New, nullptr, 0, nullptr, &cons)); + + NODE_API_CALL_RETURN_VOID(env, + napi_create_reference(env, cons, 1, &constructor)); + + NODE_API_CALL_RETURN_VOID( + env, napi_set_named_property(env, exports, "NestedWrap", cons)); +} + +napi_value NestedWrap::New(napi_env env, napi_callback_info info) { + napi_value new_target; + NODE_API_CALL(env, napi_get_new_target(env, info, &new_target)); + bool is_constructor = (new_target != nullptr); + NODE_API_BASIC_ASSERT_BASE( + is_constructor, "Constructor called without new", nullptr); + + napi_value this_val; + NODE_API_CALL(env, + napi_get_cb_info(env, info, 0, nullptr, &this_val, nullptr)); + + NestedWrap* obj = new NestedWrap(); + + obj->env_ = env; + NODE_API_CALL(env, + napi_wrap(env, + this_val, + obj, + NestedWrap::Destructor, + nullptr /* finalize_hint */, + &obj->wrapper_)); + + // Create a second napi_ref to be deleted in the destructor. + NODE_API_CALL(env, + napi_add_finalizer(env, + this_val, + obj, + NestedWrap::Destructor, + nullptr /* finalize_hint */, + &obj->nested_)); + + return this_val; +} + +static napi_value GetFinalizerCallCount(napi_env env, napi_callback_info info) { + napi_value result; + NODE_API_CALL(env, napi_create_int32(env, finalization_count, &result)); + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + NestedWrap::Init(env, exports); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", GetFinalizerCallCount), + }; + + NODE_API_CALL( + env, + napi_define_properties(env, + exports, + sizeof(descriptors) / sizeof(*descriptors), + descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/tests/js-native-api/6_object_wrap/nested_wrap.h b/tests/js-native-api/6_object_wrap/nested_wrap.h new file mode 100644 index 0000000..7e7729c --- /dev/null +++ b/tests/js-native-api/6_object_wrap/nested_wrap.h @@ -0,0 +1,33 @@ +#ifndef TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_ +#define TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_ + +#include + +/** + * Test that an napi_ref can be nested inside another ObjectWrap. + * + * This test shows a critical case where a finalizer deletes an napi_ref + * whose finalizer is also scheduled. + */ + +class NestedWrap { + public: + static void Init(napi_env env, napi_value exports); + static void Destructor(node_api_basic_env env, + void* nativeObject, + void* finalize_hint); + + private: + explicit NestedWrap(); + ~NestedWrap(); + + static napi_value New(napi_env env, napi_callback_info info); + + static napi_ref constructor; + + napi_env env_{}; + napi_ref wrapper_{}; + napi_ref nested_{}; +}; + +#endif // TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_ diff --git a/tests/js-native-api/6_object_wrap/nested_wrap.js b/tests/js-native-api/6_object_wrap/nested_wrap.js new file mode 100644 index 0000000..05e8f09 --- /dev/null +++ b/tests/js-native-api/6_object_wrap/nested_wrap.js @@ -0,0 +1,15 @@ +'use strict'; +const addon = loadAddon('nested_wrap'); + +// This test verifies that ObjectWrap and napi_ref can be nested and finalized +// correctly with a non-basic finalizer. +(() => { + let obj = new addon.NestedWrap(); + obj = null; + // Silent eslint about unused variables. + assert.strictEqual(obj, null); +})(); + +await gcUntil('object-wrap-ref', () => { + return addon.getFinalizerCallCount() === 1; +}); diff --git a/tests/js-native-api/6_object_wrap/test-basic-finalizer.js b/tests/js-native-api/6_object_wrap/test-basic-finalizer.js new file mode 100644 index 0000000..ea1f579 --- /dev/null +++ b/tests/js-native-api/6_object_wrap/test-basic-finalizer.js @@ -0,0 +1,17 @@ +'use strict'; + +// This test verifies that ObjectWrap can be correctly finalized with a +// node_api_basic_finalizer in the current JS loop tick. The addon is compiled +// with NAPI_EXPERIMENTAL, so guard behind the postFinalizer feature flag. +if (experimentalFeatures.postFinalizer) { + const addon = loadAddon('myobject_basic_finalizer'); + + (() => { + let obj = new addon.MyObject(9); + obj = null; + // Silent eslint about unused variables. + assert.strictEqual(obj, null); + })(); + + await gcUntil('basic-finalizer', () => addon.getFinalizerCallCount() === 1); +} diff --git a/tests/js-native-api/6_object_wrap/test.js b/tests/js-native-api/6_object_wrap/test.js new file mode 100644 index 0000000..191b71e --- /dev/null +++ b/tests/js-native-api/6_object_wrap/test.js @@ -0,0 +1,46 @@ +'use strict'; +const addon = loadAddon('myobject'); + +const getterOnlyErrorRE = + /^TypeError: Cannot set property .* of #<.*> which has only a getter$/; + +const valueDescriptor = Object.getOwnPropertyDescriptor( + addon.MyObject.prototype, 'value'); +const valueReadonlyDescriptor = Object.getOwnPropertyDescriptor( + addon.MyObject.prototype, 'valueReadonly'); +const plusOneDescriptor = Object.getOwnPropertyDescriptor( + addon.MyObject.prototype, 'plusOne'); +assert.strictEqual(typeof valueDescriptor.get, 'function'); +assert.strictEqual(typeof valueDescriptor.set, 'function'); +assert.strictEqual(valueDescriptor.value, undefined); +assert.strictEqual(valueDescriptor.enumerable, false); +assert.strictEqual(valueDescriptor.configurable, false); +assert.strictEqual(typeof valueReadonlyDescriptor.get, 'function'); +assert.strictEqual(valueReadonlyDescriptor.set, undefined); +assert.strictEqual(valueReadonlyDescriptor.value, undefined); +assert.strictEqual(valueReadonlyDescriptor.enumerable, false); +assert.strictEqual(valueReadonlyDescriptor.configurable, false); + +assert.strictEqual(plusOneDescriptor.get, undefined); +assert.strictEqual(plusOneDescriptor.set, undefined); +assert.strictEqual(typeof plusOneDescriptor.value, 'function'); +assert.strictEqual(plusOneDescriptor.enumerable, false); +assert.strictEqual(plusOneDescriptor.configurable, false); + +const obj = new addon.MyObject(9); +assert.strictEqual(obj.value, 9); +obj.value = 10; +assert.strictEqual(obj.value, 10); +assert.strictEqual(obj.valueReadonly, 10); +assert.throws(() => { obj.valueReadonly = 14; }, getterOnlyErrorRE); +assert.strictEqual(obj.plusOne(), 11); +assert.strictEqual(obj.plusOne(), 12); +assert.strictEqual(obj.plusOne(), 13); + +assert.strictEqual(obj.multiply().value, 13); +assert.strictEqual(obj.multiply(10).value, 130); + +const newobj = obj.multiply(-1); +assert.strictEqual(newobj.value, -13); +assert.strictEqual(newobj.valueReadonly, -13); +assert.notStrictEqual(obj, newobj);