Skip to content

Commit 2018cd6

Browse files
committed
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
1 parent ebd91a5 commit 2018cd6

16 files changed

+1689
-86
lines changed

CMakeLists.txt

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
cmake_minimum_required(VERSION 3.15...3.31)
22
project(node-api-cts)
33

4-
set(NODE_API_HEADERS_DIR ${PROJECT_SOURCE_DIR}/node_modules/node-api-headers)
5-
6-
if(NOT EXISTS ${NODE_API_HEADERS_DIR})
7-
message(FATAL_ERROR "Expected ${NODE_API_HEADERS_DIR} to exist")
8-
endif()
4+
set(NODE_API_HEADERS_DIR ${PROJECT_SOURCE_DIR}/include)
95

106
if(MSVC)
7+
# Combine layered .def files into merged .def files for import library generation
8+
set(DEF_DIR ${NODE_API_HEADERS_DIR}/def)
9+
set(DEF_HEADER "NAME NODE.EXE\nEXPORTS\n")
10+
11+
file(READ ${DEF_DIR}/js_native_api.def JS_NATIVE_API_SYMBOLS)
12+
file(READ ${DEF_DIR}/node_api.def NODE_API_SYMBOLS)
13+
file(READ ${DEF_DIR}/js_native_api_experimental.def JS_NATIVE_API_EXP_SYMBOLS)
14+
file(READ ${DEF_DIR}/node_api_experimental.def NODE_API_EXP_SYMBOLS)
15+
16+
set(NODE_API_DEF ${PROJECT_BINARY_DIR}/node_api.def)
17+
file(WRITE ${NODE_API_DEF} ${DEF_HEADER}${JS_NATIVE_API_SYMBOLS}${NODE_API_SYMBOLS})
1118
set(NODE_API_LIB ${PROJECT_BINARY_DIR}/node.lib)
12-
set(NODE_API_DEF ${NODE_API_HEADERS_DIR}/def/node_api.def)
1319
execute_process(COMMAND ${CMAKE_AR} /def:${NODE_API_DEF} /out:${NODE_API_LIB} ${CMAKE_STATIC_LINKER_FLAGS})
20+
21+
set(NODE_API_EXPERIMENTAL_DEF ${PROJECT_BINARY_DIR}/node_api_experimental.def)
22+
file(WRITE ${NODE_API_EXPERIMENTAL_DEF} ${DEF_HEADER}${JS_NATIVE_API_SYMBOLS}${NODE_API_SYMBOLS}${JS_NATIVE_API_EXP_SYMBOLS}${NODE_API_EXP_SYMBOLS})
23+
set(NODE_API_EXPERIMENTAL_LIB ${PROJECT_BINARY_DIR}/node_experimental.lib)
24+
execute_process(COMMAND ${CMAKE_AR} /def:${NODE_API_EXPERIMENTAL_DEF} /out:${NODE_API_EXPERIMENTAL_LIB} ${CMAKE_STATIC_LINKER_FLAGS})
1425
endif()
1526

1627
function(add_node_api_cts_addon ADDON_NAME)
@@ -27,12 +38,21 @@ function(add_node_api_cts_addon ADDON_NAME)
2738
if(APPLE)
2839
set_target_properties(${ADDON_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
2940
endif()
30-
target_include_directories(${ADDON_NAME} PRIVATE ${NODE_API_HEADERS_DIR}/include)
41+
target_include_directories(${ADDON_NAME} PRIVATE ${NODE_API_HEADERS_DIR})
3142
target_link_libraries(${ADDON_NAME} PRIVATE ${NODE_API_LIB})
3243
target_compile_features(${ADDON_NAME} PRIVATE cxx_std_17)
3344
target_compile_definitions(${ADDON_NAME} PRIVATE ADDON_NAME=${ADDON_NAME})
3445
endfunction()
3546

47+
function(add_node_api_cts_experimental_addon ADDON_NAME)
48+
cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "SOURCES")
49+
add_node_api_cts_addon(${ADDON_NAME} ${ARG_SOURCES})
50+
target_compile_definitions(${ADDON_NAME} PRIVATE NAPI_EXPERIMENTAL)
51+
if(MSVC)
52+
target_link_libraries(${ADDON_NAME} PRIVATE ${NODE_API_EXPERIMENTAL_LIB})
53+
endif()
54+
endfunction()
55+
3656
file(GLOB_RECURSE cmake_dirs RELATIVE ${CMAKE_SOURCE_DIR} tests/*/CMakeLists.txt)
3757

3858
foreach(cmake_file ${cmake_dirs})

PORTING.md

Lines changed: 117 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -23,87 +23,136 @@ in `js_native_api.h` and is therefore engine-agnostic.
2323
## Difficulty Ratings
2424

2525
Difficulty is assessed on two axes:
26+
2627
- **Size/complexity** — total lines of C/C++ and JS across all source files
2728
- **Runtime-API dependence** — pure `js_native_api.h` is cheapest; Node.js extensions and direct
2829
libuv calls require harness work or Node-only scoping
2930

30-
| Rating | Meaning |
31-
|---|---|
32-
| Easy | Small test, pure `js_native_api.h` or trivial runtime API, straightforward 1:1 port |
33-
| Medium | Moderate size or uses a Node.js extension API that the harness will need to abstract |
34-
| Hard | Large test and/or deep libuv/worker/SEA dependency; may need new harness primitives or Node-only scoping |
31+
| Rating | Meaning |
32+
| ------ | -------------------------------------------------------------------------------------------------------- |
33+
| Easy | Small test, pure `js_native_api.h` or trivial runtime API, straightforward 1:1 port |
34+
| Medium | Moderate size or uses a Node.js extension API that the harness will need to abstract |
35+
| Hard | Large test and/or deep libuv/worker/SEA dependency; may need new harness primitives or Node-only scoping |
3536

3637
## Engine-specific (`js-native-api`)
3738

3839
Tests covering the engine-specific part of Node-API, defined in `js_native_api.h`.
3940

40-
| Directory | Status | Difficulty |
41-
|---|---|---|
42-
| `2_function_arguments` | Ported ||
43-
| `3_callbacks` | Not ported | Easy |
44-
| `4_object_factory` | Not ported | Easy |
45-
| `5_function_factory` | Not ported | Easy |
46-
| `6_object_wrap` | Not ported | Medium |
47-
| `7_factory_wrap` | Not ported | Easy |
48-
| `8_passing_wrapped` | Not ported | Easy |
49-
| `test_array` | Not ported | Easy |
50-
| `test_bigint` | Not ported | Easy |
51-
| `test_cannot_run_js` | Not ported | Medium |
52-
| `test_constructor` | Not ported | Medium |
53-
| `test_conversions` | Not ported | Medium |
54-
| `test_dataview` | Not ported | Easy |
55-
| `test_date` | Not ported | Easy |
56-
| `test_error` | Not ported | Medium |
57-
| `test_exception` | Not ported | Medium |
58-
| `test_finalizer` | Not ported | Medium |
59-
| `test_function` | Not ported | Medium |
60-
| `test_general` | Not ported | Hard |
61-
| `test_handle_scope` | Not ported | Easy |
62-
| `test_instance_data` | Not ported | Easy |
63-
| `test_new_target` | Not ported | Easy |
64-
| `test_number` | Not ported | Easy |
65-
| `test_object` | Not ported | Hard |
66-
| `test_promise` | Not ported | Easy |
67-
| `test_properties` | Not ported | Easy |
68-
| `test_reference` | Not ported | Medium |
69-
| `test_reference_double_free` | Not ported | Easy |
70-
| `test_sharedarraybuffer` | Not ported | Medium |
71-
| `test_string` | Not ported | Medium |
72-
| `test_symbol` | Not ported | Easy |
73-
| `test_typedarray` | Not ported | Medium |
41+
| Directory | Status | Difficulty |
42+
| ---------------------------- | ---------- | ---------- |
43+
| `2_function_arguments` | Ported | |
44+
| `3_callbacks` | Not ported | Easy |
45+
| `4_object_factory` | Not ported | Easy |
46+
| `5_function_factory` | Not ported | Easy |
47+
| `6_object_wrap` | Not ported | Medium |
48+
| `7_factory_wrap` | Not ported | Easy |
49+
| `8_passing_wrapped` | Not ported | Easy |
50+
| `test_array` | Not ported | Easy |
51+
| `test_bigint` | Not ported | Easy |
52+
| `test_cannot_run_js` | Not ported | Medium |
53+
| `test_constructor` | Not ported | Medium |
54+
| `test_conversions` | Not ported | Medium |
55+
| `test_dataview` | Not ported | Easy |
56+
| `test_date` | Not ported | Easy |
57+
| `test_error` | Not ported | Medium |
58+
| `test_exception` | Not ported | Medium |
59+
| `test_finalizer` | Not ported | Medium |
60+
| `test_function` | Not ported | Medium |
61+
| `test_general` | Not ported | Hard |
62+
| `test_handle_scope` | Not ported | Easy |
63+
| `test_instance_data` | Not ported | Easy |
64+
| `test_new_target` | Not ported | Easy |
65+
| `test_number` | Not ported | Easy |
66+
| `test_object` | Not ported | Hard |
67+
| `test_promise` | Not ported | Easy |
68+
| `test_properties` | Not ported | Easy |
69+
| `test_reference` | Not ported | Medium |
70+
| `test_reference_double_free` | Not ported | Easy |
71+
| `test_sharedarraybuffer` | Not ported | Medium |
72+
| `test_string` | Not ported | Medium |
73+
| `test_symbol` | Not ported | Easy |
74+
| `test_typedarray` | Not ported | Medium |
7475

7576
## Runtime-specific (`node-api`)
7677

7778
Tests covering the runtime-specific part of Node-API, defined in `node_api.h`.
7879

79-
| Directory | Status | Difficulty |
80-
|---|---|---|
81-
| `1_hello_world` | Not ported | Easy |
82-
| `test_async` | Not ported | Hard |
83-
| `test_async_cleanup_hook` | Not ported | Hard |
84-
| `test_async_context` | Not ported | Hard |
85-
| `test_buffer` | Not ported | Medium |
86-
| `test_callback_scope` | Not ported | Hard |
87-
| `test_cleanup_hook` | Not ported | Medium |
88-
| `test_env_teardown_gc` | Not ported | Easy |
89-
| `test_exception` | Not ported | Easy |
90-
| `test_fatal` | Not ported | Hard |
91-
| `test_fatal_exception` | Not ported | Easy |
92-
| `test_general` | Not ported | Medium |
93-
| `test_init_order` | Not ported | Medium |
94-
| `test_instance_data` | Not ported | Hard |
95-
| `test_make_callback` | Not ported | Hard |
96-
| `test_make_callback_recurse` | Not ported | Hard |
97-
| `test_null_init` | Not ported | Medium |
98-
| `test_reference_by_node_api_version` | Not ported | Medium |
99-
| `test_sea_addon` | Not ported | Hard |
100-
| `test_threadsafe_function` | Not ported | Hard |
101-
| `test_threadsafe_function_shutdown` | Not ported | Hard |
102-
| `test_uv_loop` | Not ported | Hard |
103-
| `test_uv_threadpool_size` | Not ported | Hard |
104-
| `test_worker_buffer_callback` | Not ported | Hard |
105-
| `test_worker_terminate` | Not ported | Hard |
106-
| `test_worker_terminate_finalization` | Not ported | Hard |
80+
| Directory | Status | Difficulty |
81+
| ------------------------------------ | ---------- | ---------- |
82+
| `1_hello_world` | Not ported | Easy |
83+
| `test_async` | Not ported | Hard |
84+
| `test_async_cleanup_hook` | Not ported | Hard |
85+
| `test_async_context` | Not ported | Hard |
86+
| `test_buffer` | Not ported | Medium |
87+
| `test_callback_scope` | Not ported | Hard |
88+
| `test_cleanup_hook` | Not ported | Medium |
89+
| `test_env_teardown_gc` | Not ported | Easy |
90+
| `test_exception` | Not ported | Easy |
91+
| `test_fatal` | Not ported | Hard |
92+
| `test_fatal_exception` | Not ported | Easy |
93+
| `test_general` | Not ported | Medium |
94+
| `test_init_order` | Not ported | Medium |
95+
| `test_instance_data` | Not ported | Hard |
96+
| `test_make_callback` | Not ported | Hard |
97+
| `test_make_callback_recurse` | Not ported | Hard |
98+
| `test_null_init` | Not ported | Medium |
99+
| `test_reference_by_node_api_version` | Not ported | Medium |
100+
| `test_sea_addon` | Not ported | Hard |
101+
| `test_threadsafe_function` | Not ported | Hard |
102+
| `test_threadsafe_function_shutdown` | Not ported | Hard |
103+
| `test_uv_loop` | Not ported | Hard |
104+
| `test_uv_threadpool_size` | Not ported | Hard |
105+
| `test_worker_buffer_callback` | Not ported | Hard |
106+
| `test_worker_terminate` | Not ported | Hard |
107+
| `test_worker_terminate_finalization` | Not ported | Hard |
108+
109+
## Experimental Node-API Features
110+
111+
Several tests in the upstream Node.js repository use experimental APIs that are guarded behind
112+
`#ifdef NAPI_EXPERIMENTAL` in Node.js's `js_native_api.h`.
113+
114+
When `NAPI_EXPERIMENTAL` is defined in Node.js, `NAPI_VERSION` is set to
115+
`NAPI_VERSION_EXPERIMENTAL (2147483647)`. The `NAPI_MODULE` macro exports this version, and the
116+
runtime uses it to decide whether to enable experimental behavior for that addon.
117+
118+
| Feature macro | APIs | Used by |
119+
| --------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------- |
120+
| `NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER` | `node_api_is_sharedarraybuffer`, `node_api_create_sharedarraybuffer` | `test_dataview`, `test_sharedarraybuffer` |
121+
| `NODE_API_EXPERIMENTAL_HAS_CREATE_OBJECT_WITH_PROPERTIES` | `node_api_create_object_with_properties` | `test_object` |
122+
| `NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE` | `node_api_set_prototype` | `test_general` |
123+
| `NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER` | `node_api_post_finalizer` | `test_general`, `test_finalizer`, `6_object_wrap` |
124+
125+
Tests that depend on these APIs are currently ported without the experimental test cases (marked
126+
as "Partial" in the status column) or not ported at all.
127+
128+
### Infrastructure for experimental features
129+
130+
The CTS provides the following infrastructure (see [#26](https://github.com/nodejs/node-api-cts/issues/26)):
131+
132+
1. **Vendored headers** — The `include/` directory contains Node-API headers vendored directly from
133+
the Node.js repository (updated via `npm run update-headers`). These include experimental API
134+
declarations behind `#ifdef NAPI_EXPERIMENTAL`.
135+
136+
2. **CMake build function**`add_node_api_cts_experimental_addon()` compiles an addon with
137+
`NAPI_EXPERIMENTAL` defined, enabling all experimental API declarations and setting
138+
`NAPI_VERSION` to `NAPI_VERSION_EXPERIMENTAL` (2147483647).
139+
140+
3. **Implementor feature declaration** — Each runtime provides a `features.js` harness module that
141+
sets `globalThis.experimentalFeatures` to an object declaring which experimental features it
142+
supports (e.g., `{ sharedArrayBuffer: true, postFinalizer: true }`).
143+
144+
4. **Conditional test execution** — JS test files guard experimental assertions behind feature
145+
checks. When a feature is unsupported, the guarded code silently does not execute.
146+
147+
```js
148+
if (experimentalFeatures.sharedArrayBuffer) {
149+
const addon = loadAddon("test_sharedarraybuffer");
150+
// ... assertions
151+
}
152+
```
153+
154+
The `loadAddon` call must be inside the guard because addons linked against experimental
155+
symbols will fail to load on runtimes that don't export those symbols.
107156

108157
## Special Considerations
109158

@@ -144,6 +193,7 @@ The following tests call into libuv directly — `napi_get_uv_event_loop`, `uv_t
144193
- `test_uv_loop`, `test_uv_threadpool_size`
145194

146195
Porting options:
196+
147197
1. **Node-only scope** — mark these tests as Node.js-only and skip on other runtimes.
148198
2. **Harness abstraction** — introduce a minimal platform-agnostic threading/async API in the
149199
harness (e.g., `cts_thread_create`, `cts_async_schedule`) that implementors back with their

implementors/node/features.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Declares which experimental Node-API features this runtime supports.
2+
// Each key corresponds to a NODE_API_EXPERIMENTAL_HAS_* compile-time macro.
3+
// Other implementors should set unsupported features to false or omit them.
4+
globalThis.experimentalFeatures = {
5+
sharedArrayBuffer: true,
6+
createObjectWithProperties: true,
7+
setPrototype: true,
8+
postFinalizer: true,
9+
};

implementors/node/tests.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ assert(
1010

1111
const ROOT_PATH = path.resolve(import.meta.dirname, "..", "..");
1212
const TESTS_ROOT_PATH = path.join(ROOT_PATH, "tests");
13+
const FEATURES_MODULE_PATH = path.join(
14+
ROOT_PATH,
15+
"implementors",
16+
"node",
17+
"features.js"
18+
);
1319
const ASSERT_MODULE_PATH = path.join(
1420
ROOT_PATH,
1521
"implementors",
@@ -59,6 +65,8 @@ export function runFileInSubprocess(
5965
// Using file scheme prefix when to enable imports on Windows
6066
"--expose-gc",
6167
"--import",
68+
"file://" + FEATURES_MODULE_PATH,
69+
"--import",
6270
"file://" + ASSERT_MODULE_PATH,
6371
"--import",
6472
"file://" + LOAD_ADDON_MODULE_PATH,

0 commit comments

Comments
 (0)