diff --git a/.github/workflows/node-addon.yml b/.github/workflows/node-addon.yml new file mode 100644 index 0000000..8505cb0 --- /dev/null +++ b/.github/workflows/node-addon.yml @@ -0,0 +1,64 @@ +name: Node Addon Matrix + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + +jobs: + node-addon-tests: + runs-on: ${{ matrix.os }} + name: Node.js addon tests ${{ matrix.os }} node@${{ matrix.node }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, windows-latest ] + node: [ '12', '14', '16', '18', '20', '22', '24' ] + include: + - os: macos-latest + node: '20' + - os: macos-latest + node: '22' + - os: macos-latest + node: '24' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.16.0 + cache-key: node-${{ matrix.node }} + + - name: Verify Zig + run: zig version + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + - name: Build and test Node addon matrix + if: runner.os != 'Windows' + run: | + cd node-test + npm install --no-package-lock + zig build --summary all + find . -maxdepth 2 -name '*.node' -print + npm test + + - name: Build and test Node addon matrix + if: runner.os == 'Windows' + shell: pwsh + run: | + cd node-test + npm install --no-package-lock + zig build -Dtarget=x86_64-windows-msvc --summary all + Get-ChildItem -Recurse -Filter *.node | ForEach-Object { $_.FullName } + npm test diff --git a/.gitignore b/.gitignore index 9727803..3d645a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .DS_Store .zig-cache zig-out +node_modules +node-test/*.node .tmp_arkvm_runner .tmp_arkvm_memory_runner diff --git a/README.md b/README.md index a258088..a931de0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # zig-napi -This project can help us to build a native module library for OpenHarmony/HarmonyNext with zig-lang. +This project can help us to build native module libraries for OpenHarmony/HarmonyNext ArkTS and Node.js with zig-lang. ## Require @@ -40,27 +40,67 @@ pub fn build(b: *std.Build) !void { const napi = zig_napi.module("napi"); + // Build ArkTS/OpenHarmony artifacts. const result = try napi_build.nativeAddonBuild(b, .{ .name = "hello", + .napi_module = napi, .root_module_options = .{ .root_source_file = b.path("./src/hello.zig"), .target = target, .optimize = optimize, }, }); + _ = result; +} +``` + +For a Node.js addon, call `nodeAddonBuild` instead. The Node target defaults to the host target unless `root_module_options.target` is provided. + +```zig +const std = @import("std"); +const napi_build = @import("zig-napi").napi_build; + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); - if (result.arm64) |arm64| { - arm64.root_module.addImport("napi", napi); - } - if (result.arm) |arm| { - arm.root_module.addImport("napi", napi); - } - if (result.x64) |x64| { - x64.root_module.addImport("napi", napi); - } + const zig_napi = b.dependency("zig-napi", .{}); + const napi = zig_napi.module("napi"); + + const addon = try napi_build.nodeAddonBuild(b, .{ + .name = "hello", + .napi_module = napi, + .node_api = .{ + .version = .v8, + .experimental = false, + }, + .root_module_options = .{ + .root_source_file = b.path("./src/hello.zig"), + .target = target, + .optimize = optimize, + }, + }); + _ = addon; } ``` +OpenHarmony and Node addons request Node-API v8 by default. To request a newer runtime API version or experimental Node-API, configure `node_api` in `nativeAddonBuild` or `nodeAddonBuild`: + +```zig +.node_api = .{ + .version = .v10, + .experimental = false, +}, +``` + +When `.experimental = true`, the addon requests Node-API experimental version and enables experimental declarations in `napi-sys`. + +Version-gated APIs follow the same shape as NAPI-RS feature gates: for example `ThreadSafeFunction` and `Async` require v4, while `BigInt` requires v6. If an addon selects a lower version, those wrappers fail at compile time with a message pointing back to `.node_api.version`. For OpenHarmony builds, pass `.napi_module = napi` to `nativeAddonBuild` so the configured N-API version is applied to both the addon root module and the `napi` wrapper module. If type definitions compile the same source, pass the same `.node_api` to `generateTypeDefinition`. + +Node addon builds use the hand-written `src/sys/node.zig` sys layer, matching napi-rs' hand-written `napi-sys` model. OpenHarmony/ArkTS builds still use the OHOS header set under `src/sys/ohos` through `native_api.h`. + +On Windows MSVC, `nodeAddonBuild` follows napi-rs and does not require a `node.lib` lookup by default; the Node-API symbols are resolved from the current Node.js process at runtime. If a build needs to force an import library, pass `.node_import_lib`, set `NODE_LIB_FILE`, or set `NODE_LIB_DIR`. Windows GNU builds follow napi-rs' `LIBNODE_PATH`, `LIBPATH`, then `PATH` search for `libnode.dll` before linking `node`. + ## Usage ```zig @@ -98,6 +138,23 @@ zig build -Dtarget=aarch64-linux-ohos And you can get `libhello.so` in `zig-out`. +The Node.js example is in `examples/node`: + +```bash +cd examples/node +zig build +node test.js +``` + +It installs the addon as `zig-out/node/hello..node`, for example `hello.darwin-arm64.node`, `hello.linux-x64-gnu.node`, or `hello.win32-x64-msvc.node`. + +Node.js matrix tests live in `node-test`. It mirrors the NAPI-RS example split with two independent demos: + +- `node-test/napi-compat-mode` covers compat-mode style APIs and runtime-gated N-API v4/v5/v6/v7/v8 scenarios. +- `node-test/napi` covers the non compat-mode example surface such as values, strict validation, async, ThreadSafeFunction, and worker-thread loading. + +The Node addon CI runs those tests on Linux, macOS, and Windows for Node.js 12, 14, 16, 18, 20, and 22. + ## Credits This zig-napi project is heavily inspired by: diff --git a/benchmark/native-c/build.sh b/benchmark/native-c/build.sh index 4de1efd..262bddb 100755 --- a/benchmark/native-c/build.sh +++ b/benchmark/native-c/build.sh @@ -21,7 +21,7 @@ mkdir -p "${OUT_DIR}" -O3 \ -fPIC \ -shared \ - -I"${REPO_ROOT}/src/sys/header" \ + -I"${REPO_ROOT}/src/sys/ohos" \ "${EXTRA_CFLAGS[@]}" \ "${SCRIPT_DIR}/napi_benchmark.c" \ -o "${OUT_DIR}/libnapi_benchmark.so" \ diff --git a/build.zig b/build.zig index 88ae862..4f36d47 100644 --- a/build.zig +++ b/build.zig @@ -16,7 +16,8 @@ pub fn build(b: *std.Build) !void { napi.addImport("napi-sys", napi_sys); napi.addImport("build_options", build_options); + napi_sys.addImport("build_options", build_options); - napi.addIncludePath(b.path("src/sys/header")); - napi_sys.addIncludePath(b.path("src/sys/header")); + napi.addIncludePath(b.path("src/sys/ohos")); + napi_sys.addIncludePath(b.path("src/sys/ohos")); } diff --git a/docs/napi-conversion-refactor-plan.md b/docs/napi-conversion-refactor-plan.md new file mode 100644 index 0000000..2915ac7 --- /dev/null +++ b/docs/napi-conversion-refactor-plan.md @@ -0,0 +1,114 @@ +# N-API Value Conversion Refactor Plan + +## Background + +Current conversion APIs such as `Napi.from_napi_value_auto(...)` return `T` directly and use `NapiError.last_error` as an out-of-band error channel. This can produce unsafe behavior: a failed conversion may still return a placeholder/default value, and callers must remember to check `last_error` before storing or cleaning up the converted value. + +The immediate target is to align the exported-call behavior with napi-rs semantics: + +- Convert JS arguments before calling native code. +- If conversion fails, throw a JS error and return immediately. +- Never store or clean up failed conversion results. +- Treat missing JS arguments as `undefined`, not uninitialized `napi_value`. + +## Phase 1: Minimal `!T` Refactor + +Goal: make failed conversion impossible to accidentally use by changing conversion helpers from `T` to `!T`, while keeping `NapiError.last_error` temporarily for detailed error payloads. + +### Scope + +- Change `Napi.from_napi_value_fast(env, raw, T) T` to `!T`. +- Change `Napi.from_napi_value_auto(env, raw, T) T` to `!T`. +- Change `Napi.from_napi_value(env, raw, T) T` to `!T`. +- Update all direct call sites under: + - `src/napi/value/function.zig` + - `src/napi/wrapper/class.zig` + - `src/napi/value/array.zig` + - `src/napi/value/object.zig` +- Update wrapper/value `from_napi_value` implementations that currently return `T` directly: + - number + - bool + - string + - bigint + - array + - object + - buffer + - arraybuffer + - reference + - abort signal if needed + +### Required Behavior + +- Function/class entrypoints use `try`/`catch` style flow instead of reading failed values. +- `initialized_params` / `initialized_args` are incremented only after conversion succeeds. +- Array/object recursive conversion frees already-created owned values if a later element/property conversion fails. +- Missing arguments continue to be normalized to JS `undefined`. + +### Tests + +- Existing `node-test/napi/__tests__/strict.spec.js` must stay green. +- Add focused tests for nested partial-conversion failure: + - array with first element valid and second invalid + - object with first field valid and second invalid + - union variants with slice/string payloads +- Run: + - `zig build --summary failures` + - `zig build --summary failures` in `node-test` + - `just test-node-matrix` + - `git diff --check` + +### Estimate + +0.5 to 1 day. + +## Phase 2: Structured Conversion Result + +Goal: remove or sharply reduce `NapiError.last_error` by returning structured conversion errors, closer to napi-rs `Result`. + +Possible Zig shape: + +```zig +pub fn NapiResult(comptime T: type) type { + return union(enum) { + ok: T, + err: napi.Error, + }; +} +``` + +Alternative: keep Zig `!T` for control flow and add a scoped error payload object passed through conversion helpers. This avoids threadlocal state but is more invasive at call sites. + +### Scope + +- Replace `last_error`-based conversion failure propagation. +- Update async/worker/class/function error handling to consume structured errors. +- Decide public API breakage for helpers like: + - `Object.Get(...)` + - `Object.GetNamed(...)` + - `Array.Get(...)` +- Update docs and examples if these APIs become fallible. + +### Tests + +- Keep all Phase 1 tests. +- Add class constructor/method/setter invalid-argument tests. +- Add worker/async conversion error propagation tests if exposed by current API surface. +- Run Node matrix across Node 12/14/16/18/20/22 on Linux/macOS/Windows in CI. + +### Estimate + +2 to 4 days. + +## Recommended Order + +1. Land Phase 1 first to eliminate unsafe failed-value usage. +2. Expand strict invalid-input tests around array/object/class conversion. +3. Re-run CI matrix and watch for platform-specific N-API behavior. +4. Plan Phase 2 only after Phase 1 is stable, because it may require public API changes. + +## Risks + +- `Object.Get` / `Array.Get` becoming fallible may be a source-compatible breaking change. +- Array/object conversion must carefully deinit partially converted owned values. +- Some wrappers currently assume N-API conversion calls cannot fail; those assumptions need explicit handling. +- Keeping `last_error` during Phase 1 means detailed error propagation is still threadlocal, but failed values will no longer be used. diff --git a/examples/allocator-builtin/build.zig b/examples/allocator-builtin/build.zig index 14fde75..92cca36 100644 --- a/examples/allocator-builtin/build.zig +++ b/examples/allocator-builtin/build.zig @@ -10,22 +10,14 @@ pub fn build(b: *std.Build) !void { const result = try napi_build.nativeAddonBuild(b, .{ .name = "hello", + .napi_module = napi, .root_module_options = .{ .root_source_file = b.path("./src/hello.zig"), .target = target, .optimize = optimize, }, }); - - if (result.arm64) |arm64| { - arm64.root_module.addImport("napi", napi); - } - if (result.arm) |arm| { - arm.root_module.addImport("napi", napi); - } - if (result.x64) |x64| { - x64.root_module.addImport("napi", napi); - } + _ = result; const dts = try napi_build.generateTypeDefinition(b, .{ .root_source_file = b.path("./src/hello.zig"), diff --git a/examples/allocator-custom/build.zig b/examples/allocator-custom/build.zig index 14fde75..92cca36 100644 --- a/examples/allocator-custom/build.zig +++ b/examples/allocator-custom/build.zig @@ -10,22 +10,14 @@ pub fn build(b: *std.Build) !void { const result = try napi_build.nativeAddonBuild(b, .{ .name = "hello", + .napi_module = napi, .root_module_options = .{ .root_source_file = b.path("./src/hello.zig"), .target = target, .optimize = optimize, }, }); - - if (result.arm64) |arm64| { - arm64.root_module.addImport("napi", napi); - } - if (result.arm) |arm| { - arm.root_module.addImport("napi", napi); - } - if (result.x64) |x64| { - x64.root_module.addImport("napi", napi); - } + _ = result; const dts = try napi_build.generateTypeDefinition(b, .{ .root_source_file = b.path("./src/hello.zig"), diff --git a/examples/basic/build.zig b/examples/basic/build.zig index 72e261c..7224850 100644 --- a/examples/basic/build.zig +++ b/examples/basic/build.zig @@ -11,6 +11,7 @@ pub fn build(b: *std.Build) !void { const result = try napi_build.nativeAddonBuild(b, .{ .name = "hello", + .napi_module = napi, .root_module_options = .{ .root_source_file = b.path("./src/hello.zig"), .target = target, @@ -19,19 +20,16 @@ pub fn build(b: *std.Build) !void { }); if (result.arm64) |arm64| { - arm64.root_module.addImport("napi", napi); if (arm64.rootModuleTarget().abi.isOpenHarmony()) { arm64.root_module.linkSystemLibrary("hilog_ndk.z", .{}); } } if (result.arm) |arm| { - arm.root_module.addImport("napi", napi); if (arm.rootModuleTarget().abi.isOpenHarmony()) { arm.root_module.linkSystemLibrary("hilog_ndk.z", .{}); } } if (result.x64) |x64| { - x64.root_module.addImport("napi", napi); if (x64.rootModuleTarget().abi.isOpenHarmony()) { x64.root_module.linkSystemLibrary("hilog_ndk.z", .{}); } diff --git a/examples/basic/index.d.ts b/examples/basic/index.d.ts index 1b7705f..95e282c 100644 --- a/examples/basic/index.d.ts +++ b/examples/basic/index.d.ts @@ -182,9 +182,15 @@ export declare function call_thread_safe_function( ): void; export declare function test_hilog(): void; export declare function create_buffer(): Buffer; +export declare function create_empty_buffer_new(): Buffer; +export declare function create_empty_buffer_copy(): Buffer; +export declare function create_empty_external_buffer(): Buffer; export declare function get_buffer(buf: Buffer): number; export declare function get_buffer_as_string(buf: Buffer): string; export declare function create_arraybuffer(): ArrayBuffer; +export declare function create_empty_arraybuffer_new(): ArrayBuffer; +export declare function create_empty_arraybuffer_copy(): ArrayBuffer; +export declare function create_empty_external_arraybuffer(): ArrayBuffer; export declare function get_arraybuffer(buf: ArrayBuffer): number; export declare function get_arraybuffer_as_string(buf: ArrayBuffer): string; export declare function create_uint8_typedarray(): Uint8Array; diff --git a/examples/basic/src/arraybuffer.zig b/examples/basic/src/arraybuffer.zig index 3fcbc37..ceab6f6 100644 --- a/examples/basic/src/arraybuffer.zig +++ b/examples/basic/src/arraybuffer.zig @@ -4,6 +4,21 @@ pub fn create_arraybuffer(env: napi.Env) !napi.ArrayBuffer { return napi.ArrayBuffer.New(env, 1024); } +pub fn create_empty_arraybuffer_new(env: napi.Env) !napi.ArrayBuffer { + return napi.ArrayBuffer.New(env, 0); +} + +pub fn create_empty_arraybuffer_copy(env: napi.Env) !napi.ArrayBuffer { + return napi.ArrayBuffer.copy(env, &[_]u8{}); +} + +pub fn create_empty_external_arraybuffer(env: napi.Env) !napi.ArrayBuffer { + const allocator = napi.globalAllocator(); + const bytes = try allocator.alloc(u8, 0); + errdefer allocator.free(bytes); + return napi.ArrayBuffer.from(env, bytes); +} + pub fn get_arraybuffer(buf: napi.ArrayBuffer) !usize { return buf.length(); } diff --git a/examples/basic/src/buffer.zig b/examples/basic/src/buffer.zig index f8a80b4..29a56e4 100644 --- a/examples/basic/src/buffer.zig +++ b/examples/basic/src/buffer.zig @@ -4,6 +4,21 @@ pub fn create_buffer(env: napi.Env) !napi.Buffer { return napi.Buffer.New(env, 1024); } +pub fn create_empty_buffer_new(env: napi.Env) !napi.Buffer { + return napi.Buffer.New(env, 0); +} + +pub fn create_empty_buffer_copy(env: napi.Env) !napi.Buffer { + return napi.Buffer.copy(env, &[_]u8{}); +} + +pub fn create_empty_external_buffer(env: napi.Env) !napi.Buffer { + const allocator = napi.globalAllocator(); + const bytes = try allocator.alloc(u8, 0); + errdefer allocator.free(bytes); + return napi.Buffer.from(env, bytes); +} + pub fn get_buffer(buf: napi.Buffer) !usize { return buf.length(); } diff --git a/examples/basic/src/hello.zig b/examples/basic/src/hello.zig index a1f1354..3e4b93a 100644 --- a/examples/basic/src/hello.zig +++ b/examples/basic/src/hello.zig @@ -75,10 +75,16 @@ pub const TestFactoryClass = class.TestFactoryClass; pub const test_hilog = log.test_hilog; pub const create_buffer = buffer.create_buffer; +pub const create_empty_buffer_new = buffer.create_empty_buffer_new; +pub const create_empty_buffer_copy = buffer.create_empty_buffer_copy; +pub const create_empty_external_buffer = buffer.create_empty_external_buffer; pub const get_buffer = buffer.get_buffer; pub const get_buffer_as_string = buffer.get_buffer_as_string; pub const create_arraybuffer = arraybuffer.create_arraybuffer; +pub const create_empty_arraybuffer_new = arraybuffer.create_empty_arraybuffer_new; +pub const create_empty_arraybuffer_copy = arraybuffer.create_empty_arraybuffer_copy; +pub const create_empty_external_arraybuffer = arraybuffer.create_empty_external_arraybuffer; pub const get_arraybuffer = arraybuffer.get_arraybuffer; pub const get_arraybuffer_as_string = arraybuffer.get_arraybuffer_as_string; diff --git a/examples/benchmark/build.zig b/examples/benchmark/build.zig index 7cf6f9f..c379c9e 100644 --- a/examples/benchmark/build.zig +++ b/examples/benchmark/build.zig @@ -10,20 +10,12 @@ pub fn build(b: *std.Build) !void { const result = try napi_build.nativeAddonBuild(b, .{ .name = "zig_benchmark", + .napi_module = napi, .root_module_options = .{ .root_source_file = b.path("./src/hello.zig"), .target = target, .optimize = optimize, }, }); - - if (result.arm64) |arm64| { - arm64.root_module.addImport("napi", napi); - } - if (result.arm) |arm| { - arm.root_module.addImport("napi", napi); - } - if (result.x64) |x64| { - x64.root_module.addImport("napi", napi); - } + _ = result; } diff --git a/examples/init/build.zig b/examples/init/build.zig index d433bd6..2a02575 100644 --- a/examples/init/build.zig +++ b/examples/init/build.zig @@ -11,22 +11,14 @@ pub fn build(b: *std.Build) !void { const result = try napi_build.nativeAddonBuild(b, .{ .name = "hello", + .napi_module = napi, .root_module_options = .{ .root_source_file = b.path("./src/hello.zig"), .target = target, .optimize = optimize, }, }); - - if (result.arm64) |arm64| { - arm64.root_module.addImport("napi", napi); - } - if (result.arm) |arm| { - arm.root_module.addImport("napi", napi); - } - if (result.x64) |x64| { - x64.root_module.addImport("napi", napi); - } + _ = result; const dts = try napi_build.generateTypeDefinition(b, .{ .root_source_file = b.path("./src/hello.zig"), diff --git a/examples/memory/build.zig b/examples/memory/build.zig index d3e3db9..2090532 100644 --- a/examples/memory/build.zig +++ b/examples/memory/build.zig @@ -10,20 +10,12 @@ pub fn build(b: *std.Build) !void { const result = try napi_build.nativeAddonBuild(b, .{ .name = "hello", + .napi_module = napi, .root_module_options = .{ .root_source_file = b.path("./src/hello.zig"), .target = target, .optimize = optimize, }, }); - - if (result.arm64) |arm64| { - arm64.root_module.addImport("napi", napi); - } - if (result.arm) |arm| { - arm.root_module.addImport("napi", napi); - } - if (result.x64) |x64| { - x64.root_module.addImport("napi", napi); - } + _ = result; } diff --git a/examples/node/build.zig b/examples/node/build.zig new file mode 100644 index 0000000..c2d3fa3 --- /dev/null +++ b/examples/node/build.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const napi_build = @import("zig-napi").napi_build; + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const zig_napi = b.dependency("zig-napi", .{}); + const napi = zig_napi.module("napi"); + + const addon = try napi_build.nodeAddonBuild(b, .{ + .name = "hello", + .napi_module = napi, + .node_api = .{ + .version = .v8, + .experimental = false, + }, + .root_module_options = .{ + .root_source_file = b.path("src/hello.zig"), + .target = target, + .optimize = optimize, + }, + }); + _ = addon; +} diff --git a/examples/node/build.zig.zon b/examples/node/build.zig.zon new file mode 100644 index 0000000..fad8760 --- /dev/null +++ b/examples/node/build.zig.zon @@ -0,0 +1,8 @@ +.{ + .name = .node_example, + .version = "0.0.1", + .minimum_zig_version = "0.16.0", + .fingerprint = 0xc8e4deed2741664f, + .dependencies = .{ .@"zig-napi" = .{ .path = "../.." } }, + .paths = .{ "build.zig", "build.zig.zon", "src", "test.js" }, +} diff --git a/examples/node/src/hello.zig b/examples/node/src/hello.zig new file mode 100644 index 0000000..8ea2d37 --- /dev/null +++ b/examples/node/src/hello.zig @@ -0,0 +1,17 @@ +const napi = @import("napi"); + +pub fn add(left: i32, right: i32) i32 { + return left + right; +} + +pub fn hello() []const u8 { + return "hello from node"; +} + +pub fn requestedNapiVersion() i32 { + return @intFromEnum(napi.selectedNapiVersion()); +} + +comptime { + napi.NODE_API_MODULE("hello", @This()); +} diff --git a/examples/node/test.js b/examples/node/test.js new file mode 100644 index 0000000..ac9232c --- /dev/null +++ b/examples/node/test.js @@ -0,0 +1,36 @@ +const assert = require("assert").strict; +const fs = require("fs"); +const path = require("path"); + +function loadAddon() { + return loadAddonVariant("hello"); +} + +function addonCandidates(name) { + const nodeOut = path.join(__dirname, "zig-out", "node"); + return fs + .readdirSync(nodeOut, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => path.join(nodeOut, entry.name, `${name}.node`)) + .filter((candidate) => fs.existsSync(candidate)); +} + +function loadAddonVariant(name) { + for (const candidate of addonCandidates(name)) { + try { + return require(candidate); + } catch (error) { + if (error && error.code !== "ERR_DLOPEN_FAILED") { + throw error; + } + } + } + + throw new Error(`Unable to load a host-compatible ${name}.node`); +} + +const addon = loadAddon(); + +assert.equal(addon.add(20, 22), 42); +assert.equal(addon.hello(), "hello from node"); +assert.equal(addon.requestedNapiVersion(), 8); diff --git a/justfile b/justfile index 34edbfd..425dac1 100644 --- a/justfile +++ b/justfile @@ -20,6 +20,7 @@ build-example: for example in examples/*; do [[ -f "$example/build.zig" ]] || continue + [[ "$(basename "$example")" == "node" ]] && continue for target in "${targets[@]}"; do echo "==> $example ($target)" @@ -27,6 +28,27 @@ build-example: done done +build-node-example: + #!/usr/bin/env bash + set -euo pipefail + + cd examples/node + zig build + node test.js + +test-node-matrix: + #!/usr/bin/env bash + set -euo pipefail + + cd node-test + npm install --no-package-lock + target_args="" + case "$(uname -s)" in + MINGW*|MSYS*|CYGWIN*) target_args="-Dtarget=x86_64-windows-msvc" ;; + esac + zig build $target_args + npm test + format: zig fmt $(git ls-files '*.zig' '*.zon') oxk format $(git ls-files '*.js' '*.jsx' '*.ts' '*.tsx' '*.ets') diff --git a/memory-testing/binary.ts b/memory-testing/binary.ts index 63dc884..d3c1e64 100644 --- a/memory-testing/binary.ts +++ b/memory-testing/binary.ts @@ -7,14 +7,24 @@ export function exerciseBinaryWrappers(native: NativeAddon) { const bufferValue = native.create_buffer_copy(64); assertEqual(native.buffer_length(bufferValue), 64, "buffer copy length"); assertEqual(native.buffer_first_byte(bufferValue), 0, "buffer first byte"); + assertEqual(native.buffer_length(native.create_buffer_copy(0)), 0, "empty buffer copy length"); const newBufferValue = native.create_buffer_new(32); assertEqual(native.buffer_length(newBufferValue), 32, "buffer new length"); assertEqual(native.buffer_first_byte(newBufferValue), 0x5a, "buffer new first byte"); + assertEqual(native.buffer_length(native.create_buffer_new(0)), 0, "empty buffer new length"); + + const emptyExternalBufferValue = native.create_external_buffer(0); + assertEqual(native.buffer_length(emptyExternalBufferValue), 0, "empty external buffer length"); const arrayBufferValue = native.create_arraybuffer_copy(64); assertEqual(native.arraybuffer_length(arrayBufferValue), 64, "arraybuffer copy length"); assertEqual(native.arraybuffer_first_byte(arrayBufferValue), 3, "arraybuffer first byte"); + assertEqual( + native.arraybuffer_length(native.create_arraybuffer_copy(0)), + 0, + "empty arraybuffer copy length", + ); const newArrayBufferValue = native.create_arraybuffer_new(32); assertEqual(native.arraybuffer_length(newArrayBufferValue), 32, "arraybuffer new length"); @@ -23,6 +33,18 @@ export function exerciseBinaryWrappers(native: NativeAddon) { 0x6b, "arraybuffer new first byte", ); + assertEqual( + native.arraybuffer_length(native.create_arraybuffer_new(0)), + 0, + "empty arraybuffer new length", + ); + + const emptyExternalArrayBufferValue = native.create_external_arraybuffer(0); + assertEqual( + native.arraybuffer_length(emptyExternalArrayBufferValue), + 0, + "empty external arraybuffer length", + ); const typedArrayValue = native.create_uint8_typedarray_copy(); assertEqual(native.typedarray_sum(typedArrayValue), 10, "typedarray created sum"); diff --git a/node-test/build.zig b/node-test/build.zig new file mode 100644 index 0000000..059d7d9 --- /dev/null +++ b/node-test/build.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const napi_build = @import("zig-napi").napi_build; + +fn addNodeAddon( + b: *std.Build, + napi: *std.Build.Module, + name: []const u8, + source: []const u8, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) !void { + const addon = try napi_build.nodeAddonBuild(b, .{ + .name = name, + .napi_module = napi, + .node_api = .{ + // Keep the node-version matrix loadable on Node 12 while still + // covering the N-API v4/v5/v6/v7/v8 gated surfaces. + .version = .v8, + .experimental = false, + }, + .root_module_options = .{ + .root_source_file = b.path(source), + .target = target, + .optimize = optimize, + .link_libc = true, + }, + }); + const npm_root_install = b.addInstallFileWithDir( + addon.getEmittedBin(), + .{ .custom = ".." }, + napi_build.nodeAddonFilename(b, name, target), + ); + b.getInstallStep().dependOn(&npm_root_install.step); +} + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const zig_napi = b.dependency("zig-napi", .{}); + const napi = zig_napi.module("napi"); + + try addNodeAddon( + b, + napi, + "compat_mode", + "napi-compat-mode/src/lib.zig", + target, + optimize, + ); + + try addNodeAddon( + b, + napi, + "example", + "napi/src/lib.zig", + target, + optimize, + ); +} diff --git a/node-test/build.zig.zon b/node-test/build.zig.zon new file mode 100644 index 0000000..28fddec --- /dev/null +++ b/node-test/build.zig.zon @@ -0,0 +1,14 @@ +.{ + .name = .node_test, + .version = "0.0.1", + .minimum_zig_version = "0.16.0", + .fingerprint = 0x1fdbf39a616e422c, + .dependencies = .{ .@"zig-napi" = .{ .path = ".." } }, + .paths = .{ + "build.zig", + "build.zig.zon", + "package.json", + "napi-compat-mode", + "napi", + }, +} diff --git a/node-test/load-addon.js b/node-test/load-addon.js new file mode 100644 index 0000000..ce1c748 --- /dev/null +++ b/node-test/load-addon.js @@ -0,0 +1,50 @@ +const fs = require("fs"); +const path = require("path"); + +function platformArchABIs() { + const arch = process.arch; + const variables = process.config && process.config.variables ? process.config.variables : {}; + + switch (process.platform) { + case "darwin": + return [`darwin-${arch}`]; + case "win32": + if (variables.shlib_suffix === "dll.a" || variables.node_target_type === "shared_library") { + return [`win32-${arch}-gnu`, `win32-${arch}-msvc`]; + } + return [`win32-${arch}-msvc`, `win32-${arch}-gnu`]; + case "linux": + return [`linux-${arch}-gnu`, `linux-${arch}-musl`]; + case "freebsd": + return [`freebsd-${arch}`]; + default: + return [`${process.platform}-${arch}`]; + } +} + +module.exports = function loadAddon(name) { + const candidates = platformArchABIs().map((platformArchABI) => + path.join(__dirname, `${name}.${platformArchABI}.node`), + ); + const loadErrors = []; + + for (const candidate of candidates) { + if (!fs.existsSync(candidate)) { + loadErrors.push(new Error(`Missing native binding ${candidate}`)); + continue; + } + + try { + return require(candidate); + } catch (error) { + loadErrors.push(error); + } + } + + throw new Error( + [ + `Unable to load ${name}.node from: ${candidates.join(", ")}`, + ...loadErrors.map((error) => `- ${error && error.message ? error.message : error}`), + ].join("\n"), + ); +}; diff --git a/node-test/napi-compat-mode/__tests__/array.spec.js b/node-test/napi-compat-mode/__tests__/array.spec.js new file mode 100644 index 0000000..b3ef57d --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/array.spec.js @@ -0,0 +1,46 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("should be able to create array", (t) => { + const arr = bindings.testCreateArray(); + t.true(arr instanceof Array); + t.true(Array.isArray(arr)); + arr.push(1, 2, 3); + t.deepEqual(arr, [1, 2, 3]); +}); + +test("should be able to create array with length", (t) => { + const len = 100; + const arr = bindings.testCreateArrayWithLength(len); + t.true(arr instanceof Array); + t.true(Array.isArray(arr)); + t.is(arr.length, len); +}); + +test("should be able to set element", (t) => { + const obj = {}; + const index = 29; + const arr = []; + bindings.testSetElement(arr, index, obj); + t.is(arr[index], obj); +}); + +test("should be able to use has_element", (t) => { + const arr = [1, "3", undefined]; + const index = 29; + arr[index] = {}; + t.true(bindings.testHasElement(arr, 0)); + t.true(bindings.testHasElement(arr, 1)); + t.true(bindings.testHasElement(arr, 2)); + t.false(bindings.testHasElement(arr, 3)); + t.false(bindings.testHasElement(arr, 10)); + t.true(bindings.testHasElement(arr, index)); +}); + +test("should be able to delete element", (t) => { + const arr = [0, 1, 2, 3]; + for (const [index] of arr.entries()) { + t.true(bindings.testDeleteElement(arr, index)); + t.true(arr[index] === undefined); + } +}); diff --git a/node-test/napi-compat-mode/__tests__/arraybuffer.spec.js b/node-test/napi-compat-mode/__tests__/arraybuffer.spec.js new file mode 100644 index 0000000..389fc45 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/arraybuffer.spec.js @@ -0,0 +1,57 @@ +const ava = require("ava"); +const bindings = require("./binding"); +const { napiVersion } = require("./napi-version"); + +const test = napiVersion >= 6 ? ava : ava.skip; + +test("should get arraybuffer length", (t) => { + const fixture = Buffer.from("wow, hello"); + t.is(bindings.getArraybufferLength(fixture.buffer), fixture.buffer.byteLength); +}); + +test("should create empty arraybuffer", (t) => { + t.is(bindings.createEmptyArraybufferFromNew().byteLength, 0); + t.is(bindings.createEmptyArraybufferFromData().byteLength, 0); + t.is(bindings.createEmptyExternalArraybuffer().byteLength, 0); +}); + +test("should create external arraybuffer", (t) => { + t.deepEqual(Array.from(new Uint8Array(bindings.createExternalArraybuffer())), [5, 6, 7, 8]); +}); + +test("should be able to mutate Uint8Array", (t) => { + const fixture = new Uint8Array([0, 1, 2]); + bindings.mutateUint8Array(fixture); + t.is(fixture[0], 42); +}); + +test("should be able to mutate Uint8Array in its middle", (t) => { + const fixture = new Uint8Array([0, 1, 2]); + const view = new Uint8Array(fixture.buffer, 1, 1); + bindings.mutateUint8Array(view); + t.is(fixture[1], 42); +}); + +test("should be able to mutate Uint16Array", (t) => { + const fixture = new Uint16Array([0, 1, 2]); + bindings.mutateUint16Array(fixture); + t.is(fixture[0], 65535); +}); + +test("should be able to mutate Int16Array", (t) => { + const fixture = new Int16Array([0, 1, 2]); + bindings.mutateInt16Array(fixture); + t.is(fixture[0], 32767); +}); + +test("should be able to mutate Float32Array", (t) => { + const fixture = new Float32Array([0, 1, 2]); + bindings.mutateFloat32Array(fixture); + t.true(Math.abs(fixture[0] - 3.33) <= 0.0001); +}); + +test("should be able to mutate Float64Array", (t) => { + const fixture = new Float64Array([0, 1, 2]); + bindings.mutateFloat64Array(fixture); + t.true(Math.abs(fixture[0] - Math.PI) <= 0.0000001); +}); diff --git a/node-test/napi-compat-mode/__tests__/binding.js b/node-test/napi-compat-mode/__tests__/binding.js new file mode 100644 index 0000000..e3e14e4 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/binding.js @@ -0,0 +1,3 @@ +const path = require("path"); + +module.exports = require(path.join(__dirname, "..", "..", "load-addon"))("compat_mode"); diff --git a/node-test/napi-compat-mode/__tests__/buffer.spec.js b/node-test/napi-compat-mode/__tests__/buffer.spec.js new file mode 100644 index 0000000..ee46e44 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/buffer.spec.js @@ -0,0 +1,39 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("should get buffer length", (t) => { + const fixture = Buffer.from("wow, hello"); + t.is(bindings.getBufferLength(fixture), fixture.length); +}); + +test("should stringify buffer", (t) => { + const fixture = "wow, hello"; + t.is(bindings.bufferToString(Buffer.from(fixture)), fixture); +}); + +test("should copy", (t) => { + const fixture = Buffer.from("wow, hello"); + const copyBuffer = bindings.copyBuffer(fixture); + t.deepEqual(copyBuffer, fixture); + t.not(fixture, copyBuffer); +}); + +test("should create borrowed buffer with noop finalize", (t) => { + t.deepEqual(bindings.createBorrowedBufferWithNoopFinalize(), Buffer.from([1, 2, 3])); +}); + +test("should create borrowed buffer with finalize", (t) => { + t.deepEqual(bindings.createBorrowedBufferWithFinalize(), Buffer.from([1, 2, 3])); +}); + +test("should create empty buffer", (t) => { + t.is(bindings.createEmptyBuffer().toString(), ""); + t.is(bindings.createEmptyBufferFromNew().length, 0); + t.is(bindings.createEmptyExternalBuffer().length, 0); +}); + +test("should be able to mutate buffer", (t) => { + const fixture = Buffer.from([0, 1]); + bindings.mutateBuffer(fixture); + t.is(fixture[1], 42); +}); diff --git a/node-test/napi-compat-mode/__tests__/either.spec.js b/node-test/napi-compat-mode/__tests__/either.spec.js new file mode 100644 index 0000000..55081ab --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/either.spec.js @@ -0,0 +1,12 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("either should work", (t) => { + const fixture = "napi"; + t.is(bindings.eitherNumberString(1), 101); + t.is(bindings.eitherNumberString(fixture), `Either::B(${fixture})`); +}); + +test("dynamic argument length should work", (t) => { + t.is(bindings.dynamicArgumentLength(1), 101); +}); diff --git a/node-test/napi-compat-mode/__tests__/function.spec.js b/node-test/napi-compat-mode/__tests__/function.spec.js new file mode 100644 index 0000000..80524dc --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/function.spec.js @@ -0,0 +1,29 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("should call the function", (t) => { + bindings.testCallFunction((arg1, arg2) => { + t.is(`${arg1} ${arg2}`, "hello world"); + }); +}); + +test("should call function with ref args", (t) => { + bindings.testCallFunctionWithRefArguments((arg1, arg2) => { + t.is(`${arg1} ${arg2}`, "hello world"); + }); +}); + +test("should handle errors", (t) => { + bindings.testCallFunctionError( + () => { + throw new Error("Testing"); + }, + (message) => { + t.is(message, "Testing"); + }, + ); +}); + +test("should be able to create function from closure", (t) => { + t.is(bindings.testCreateFunctionFromClosure()(1), "arguments length: 1"); +}); diff --git a/node-test/napi-compat-mode/__tests__/get-napi-version.spec.js b/node-test/napi-compat-mode/__tests__/get-napi-version.spec.js new file mode 100644 index 0000000..80b3cd3 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/get-napi-version.spec.js @@ -0,0 +1,8 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("should get napi version", (t) => { + const napiVersion = bindings.getNapiVersion(); + t.is(typeof napiVersion, "number"); + t.is(`${napiVersion}`, process.versions.napi); +}); diff --git a/node-test/napi-compat-mode/__tests__/napi-version.js b/node-test/napi-compat-mode/__tests__/napi-version.js new file mode 100644 index 0000000..ecd74ce --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/napi-version.js @@ -0,0 +1 @@ +exports.napiVersion = Number.parseInt(process.versions.napi || "1", 10); diff --git a/node-test/napi-compat-mode/__tests__/napi4/deferred.spec.js b/node-test/napi-compat-mode/__tests__/napi4/deferred.spec.js new file mode 100644 index 0000000..06afe46 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/napi4/deferred.spec.js @@ -0,0 +1,9 @@ +const ava = require("ava"); +const bindings = require("../binding"); +const { napiVersion } = require("../napi-version"); + +const test = napiVersion >= 4 ? ava : ava.skip; + +test("should resolve deferred from background thread", async (t) => { + t.is(await bindings.doubleAsync(21), 42); +}); diff --git a/node-test/napi-compat-mode/__tests__/napi4/threadsafe_function.spec.js b/node-test/napi-compat-mode/__tests__/napi4/threadsafe_function.spec.js new file mode 100644 index 0000000..25d4431 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/napi4/threadsafe_function.spec.js @@ -0,0 +1,19 @@ +const ava = require("ava"); +const bindings = require("../binding"); +const { napiVersion } = require("../napi-version"); + +const test = napiVersion >= 4 ? ava : ava.skip; + +test("should get js function called from a thread", async (t) => { + await new Promise((resolve, reject) => { + bindings.callThreadsafeFunction((err, left, right) => { + try { + t.is(err, null); + t.is(left + right, 3); + resolve(); + } catch (error) { + reject(error); + } + }); + }); +}); diff --git a/node-test/napi-compat-mode/__tests__/napi5/date.spec.js b/node-test/napi-compat-mode/__tests__/napi5/date.spec.js new file mode 100644 index 0000000..2daee96 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/napi5/date.spec.js @@ -0,0 +1,24 @@ +const ava = require("ava"); +const bindings = require("../binding"); +const { napiVersion } = require("../napi-version"); + +const test = napiVersion >= 5 ? ava : ava.skip; + +test("should return false if value is not date", (t) => { + t.false(bindings.isDate(1)); +}); + +test("should return true if value is date", (t) => { + t.true(bindings.isDate(new Date())); +}); + +test("should create date", (t) => { + const date = bindings.createDate(1000); + t.true(date instanceof Date); + t.is(date.valueOf(), 1000); +}); + +test("should get date value", (t) => { + const date = new Date(1000); + t.is(bindings.getDateValue(date), date.valueOf()); +}); diff --git a/node-test/napi-compat-mode/__tests__/napi6/bigint.spec.js b/node-test/napi-compat-mode/__tests__/napi6/bigint.spec.js new file mode 100644 index 0000000..7373ac6 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/napi6/bigint.spec.js @@ -0,0 +1,20 @@ +const ava = require("ava"); +const bindings = require("../binding"); +const { napiVersion } = require("../napi-version"); + +const test = napiVersion >= 6 ? ava : ava.skip; + +test("should create bigints", (t) => { + t.is(bindings.createBigInt(), 9007199254740993n); + t.is(bindings.bigintAdd(20n, 22n), 42n); +}); + +test("should get integers from bigints", (t) => { + t.is(bindings.bigintToI64(42n), 42); +}); + +test("should be able to mutate BigInt64Array", (t) => { + const fixture = new BigInt64Array([0n, 1n, 2n]); + bindings.mutateI64Array(fixture); + t.deepEqual(fixture[0], 9223372036854775807n); +}); diff --git a/node-test/napi-compat-mode/__tests__/napi7/arraybuffer.spec.js b/node-test/napi-compat-mode/__tests__/napi7/arraybuffer.spec.js new file mode 100644 index 0000000..63f221c --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/napi7/arraybuffer.spec.js @@ -0,0 +1,29 @@ +const ava = require("ava"); +const bindings = require("../binding"); +const { napiVersion } = require("../napi-version"); + +const test = napiVersion >= 7 ? ava : ava.skip; + +test("should be able to detach ArrayBuffer", (t) => { + const buf = Buffer.from("hello world"); + const ab = buf.buffer.slice(0, buf.length); + try { + bindings.detachArrayBuffer(ab); + t.is(ab.byteLength, 0); + } catch (e) { + t.is(e.code, "DetachableArraybufferExpected"); + } +}); + +test("is detached arraybuffer should work fine", (t) => { + const buf = Buffer.from("hello world"); + const ab = buf.buffer.slice(0, buf.length); + try { + bindings.detachArrayBuffer(ab); + const nonDetachedArrayBuffer = new ArrayBuffer(10); + t.true(bindings.isDetachedArrayBuffer(ab)); + t.false(bindings.isDetachedArrayBuffer(nonDetachedArrayBuffer)); + } catch (e) { + t.is(e.code, "DetachableArraybufferExpected"); + } +}); diff --git a/node-test/napi-compat-mode/__tests__/napi8/object.spec.js b/node-test/napi-compat-mode/__tests__/napi8/object.spec.js new file mode 100644 index 0000000..27cb2e6 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/napi8/object.spec.js @@ -0,0 +1,17 @@ +const ava = require("ava"); +const bindings = require("../binding"); +const { napiVersion } = require("../napi-version"); + +const test = napiVersion >= 8 ? ava : ava.skip; + +test("should be able to freeze object", (t) => { + const object = { value: 1 }; + t.is(bindings.freezeObject(object), object); + t.true(Object.isFrozen(object)); +}); + +test("should be able to seal object", (t) => { + const object = { value: 1 }; + t.is(bindings.sealObject(object), object); + t.true(Object.isSealed(object)); +}); diff --git a/node-test/napi-compat-mode/__tests__/string.spec.js b/node-test/napi-compat-mode/__tests__/string.spec.js new file mode 100644 index 0000000..0232619 --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/string.spec.js @@ -0,0 +1,26 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("should be able to concat string", (t) => { + const fixture = "JavaScript 🌳 你好 napi"; + t.is(bindings.concatString(fixture), "JavaScript 🌳 你好 napi + Rust 🦀 string!"); +}); + +test("should be able to concat string with char \\0", (t) => { + const fixture = "JavaScript \0 🌳 你好 \0 napi"; + t.is(bindings.concatString(fixture), "JavaScript \0 🌳 你好 \0 napi + Rust 🦀 string!"); +}); + +test("should be able to concat utf16 string", (t) => { + const fixture = "JavaScript 🌳 你好 napi"; + t.is(bindings.concatUTF16String(fixture), "JavaScript 🌳 你好 napi + Rust 🦀 string!"); +}); + +test("should be able to concat latin1 string", (t) => { + const fixture = "æ¶½¾DEL"; + t.is(bindings.concatLatin1String(fixture), "æ¶½¾DEL + Rust 🦀 string!"); +}); + +test("should be able to crate latin1 string", (t) => { + t.is(bindings.createLatin1(), "©¿"); +}); diff --git a/node-test/napi-compat-mode/__tests__/throw.spec.js b/node-test/napi-compat-mode/__tests__/throw.spec.js new file mode 100644 index 0000000..353f15f --- /dev/null +++ b/node-test/napi-compat-mode/__tests__/throw.spec.js @@ -0,0 +1,16 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("should be able to throw error from native", (t) => { + t.throws(bindings.testThrow); +}); + +test("should be able to throw error from native with reason", (t) => { + const reason = "Fatal"; + const error = t.throws(() => bindings.testThrowWithReason(reason)); + t.regex(error.message, /Fatal/); +}); + +test("should throw if Rust code panic", (t) => { + t.throws(() => bindings.testThrowWithPanic()); +}); diff --git a/node-test/napi-compat-mode/src/array.zig b/node-test/napi-compat-mode/src/array.zig new file mode 100644 index 0000000..80983b8 --- /dev/null +++ b/node-test/napi-compat-mode/src/array.zig @@ -0,0 +1,32 @@ +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +pub fn testCreateArray(env: napi.Env) !napi.Array { + return try napi.Array.Create(env); +} + +pub fn testCreateArrayWithLength(env: napi.Env, len: u32) !c.napi_value { + var raw: c.napi_value = undefined; + const status = c.napi_create_array_with_length(env.raw, len, &raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return raw; +} + +pub fn testSetElement(array: napi.Array, index: u32, value: napi.Object) !void { + const status = c.napi_set_element(array.env, array.raw, index, value.raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); +} + +pub fn testHasElement(array: napi.Array, index: u32) !bool { + var result = false; + const status = c.napi_has_element(array.env, array.raw, index, &result); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return result; +} + +pub fn testDeleteElement(array: napi.Array, index: u32) !bool { + var result = false; + const status = c.napi_delete_element(array.env, array.raw, index, &result); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return result; +} diff --git a/node-test/napi-compat-mode/src/arraybuffer.zig b/node-test/napi-compat-mode/src/arraybuffer.zig new file mode 100644 index 0000000..df85b76 --- /dev/null +++ b/node-test/napi-compat-mode/src/arraybuffer.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const napi = @import("napi"); + +pub fn getArraybufferLength(buffer: napi.ArrayBuffer) usize { + return buffer.length(); +} + +pub fn createEmptyArraybufferFromNew(env: napi.Env) !napi.ArrayBuffer { + return try napi.ArrayBuffer.New(env, 0); +} + +pub fn createEmptyArraybufferFromData(env: napi.Env) !napi.ArrayBuffer { + return try napi.ArrayBuffer.copy(env, &[_]u8{}); +} + +pub fn createEmptyExternalArraybuffer(env: napi.Env) !napi.ArrayBuffer { + const allocator = napi.globalAllocator(); + const bytes = try allocator.alloc(u8, 0); + errdefer allocator.free(bytes); + return try napi.ArrayBuffer.from(env, bytes); +} + +pub fn createExternalArraybuffer(env: napi.Env) !napi.ArrayBuffer { + const allocator = napi.globalAllocator(); + const bytes = try allocator.alloc(u8, 4); + errdefer allocator.free(bytes); + @memcpy(bytes, &[_]u8{ 5, 6, 7, 8 }); + return try napi.ArrayBuffer.from(env, bytes); +} + +pub fn mutateUint8Array(values: napi.Uint8Array) void { + if (values.length() > 0) values.asSlice()[0] = 42; +} + +pub fn mutateUint16Array(values: napi.Uint16Array) void { + if (values.length() > 0) values.asSlice()[0] = 65535; +} + +pub fn mutateInt16Array(values: napi.Int16Array) void { + if (values.length() > 0) values.asSlice()[0] = 32767; +} + +pub fn mutateFloat32Array(values: napi.Float32Array) void { + if (values.length() > 0) values.asSlice()[0] = 3.33; +} + +pub fn mutateFloat64Array(values: napi.Float64Array) void { + if (values.length() > 0) values.asSlice()[0] = std.math.pi; +} diff --git a/node-test/napi-compat-mode/src/buffer.zig b/node-test/napi-compat-mode/src/buffer.zig new file mode 100644 index 0000000..1a1dc0c --- /dev/null +++ b/node-test/napi-compat-mode/src/buffer.zig @@ -0,0 +1,55 @@ +const napi = @import("napi"); + +fn noopFinalize() void {} + +pub fn getBufferLength(buffer: napi.Buffer) usize { + return buffer.length(); +} + +pub fn bufferToString(buffer: napi.Buffer) ![]u8 { + const allocator = napi.globalAllocator(); + const out = try allocator.alloc(u8, buffer.length()); + @memcpy(out, buffer.asConstSlice()); + return out; +} + +pub fn copyBuffer(env: napi.Env, buffer: napi.Buffer) !napi.Buffer { + return try napi.Buffer.copy(env, buffer.asConstSlice()); +} + +pub fn createBorrowedBufferWithNoopFinalize(env: napi.Env) !napi.Buffer { + const allocator = napi.globalAllocator(); + const bytes = try allocator.alloc(u8, 3); + errdefer allocator.free(bytes); + @memcpy(bytes, &[_]u8{ 1, 2, 3 }); + return try napi.Buffer.fromWithFinalizer(env, bytes, null); +} + +pub fn createBorrowedBufferWithFinalize(env: napi.Env) !napi.Buffer { + const allocator = napi.globalAllocator(); + const bytes = try allocator.alloc(u8, 3); + errdefer allocator.free(bytes); + @memcpy(bytes, &[_]u8{ 1, 2, 3 }); + return try napi.Buffer.fromWithFinalizer(env, bytes, noopFinalize); +} + +pub fn createEmptyBuffer(env: napi.Env) !napi.Buffer { + return try napi.Buffer.copy(env, &[_]u8{}); +} + +pub fn createEmptyBufferFromNew(env: napi.Env) !napi.Buffer { + return try napi.Buffer.New(env, 0); +} + +pub fn createEmptyExternalBuffer(env: napi.Env) !napi.Buffer { + const allocator = napi.globalAllocator(); + const bytes = try allocator.alloc(u8, 0); + errdefer allocator.free(bytes); + return try napi.Buffer.from(env, bytes); +} + +pub fn mutateBuffer(buffer: napi.Buffer) void { + if (buffer.length() > 1) { + buffer.asSlice()[1] = 42; + } +} diff --git a/node-test/napi-compat-mode/src/either.zig b/node-test/napi-compat-mode/src/either.zig new file mode 100644 index 0000000..1f6eda8 --- /dev/null +++ b/node-test/napi-compat-mode/src/either.zig @@ -0,0 +1,38 @@ +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +const NumberOrString = union(enum) { + number: i32, + string: []const u8, +}; + +pub fn eitherNumberString(env: napi.Env, value: NumberOrString) !c.napi_value { + switch (value) { + .number => |number| { + var raw: c.napi_value = undefined; + const status = c.napi_create_int32(env.raw, number + 100, &raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return raw; + }, + .string => |string| { + const prefix = "Either::B("; + const suffix = ")"; + const allocator = napi.globalAllocator(); + const out = try allocator.alloc(u8, prefix.len + string.len + suffix.len); + defer allocator.free(out); + + @memcpy(out[0..prefix.len], prefix); + @memcpy(out[prefix.len .. prefix.len + string.len], string); + @memcpy(out[prefix.len + string.len ..], suffix); + + var raw: c.napi_value = undefined; + const status = c.napi_create_string_utf8(env.raw, out.ptr, out.len, &raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return raw; + }, + } +} + +pub fn dynamicArgumentLength(value: i32) i32 { + return value + 100; +} diff --git a/node-test/napi-compat-mode/src/function.zig b/node-test/napi-compat-mode/src/function.zig new file mode 100644 index 0000000..2319ed0 --- /dev/null +++ b/node-test/napi-compat-mode/src/function.zig @@ -0,0 +1,35 @@ +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +pub fn testCallFunction(callback: napi.Function(struct { []const u8, []const u8 }, napi.Undefined)) !void { + _ = try callback.Call(.{ "hello", "world" }); +} + +pub fn testCallFunctionWithRefArguments(callback: napi.Function(struct { []const u8, []const u8 }, napi.Undefined)) !void { + _ = try callback.Call(.{ "hello", "world" }); +} + +pub fn testCallFunctionError(callback: napi.Function(struct {}, napi.Undefined), error_callback: napi.Function([]const u8, napi.Undefined)) void { + var undefined_value: c.napi_value = undefined; + _ = c.napi_get_undefined(callback.env, &undefined_value); + + var result: c.napi_value = undefined; + const call_status = c.napi_call_function(callback.env, undefined_value, callback.raw, 0, null, &result); + if (call_status != c.napi_ok) { + var pending = false; + _ = c.napi_is_exception_pending(callback.env, &pending); + if (pending) { + var exception: c.napi_value = undefined; + _ = c.napi_get_and_clear_last_exception(callback.env, &exception); + } + _ = error_callback.Call("Testing") catch return; + } +} + +fn argumentsLength(_: i32) []const u8 { + return "arguments length: 1"; +} + +pub fn testCreateFunctionFromClosure(env: napi.Env) !napi.Function(i32, []const u8) { + return try napi.Function(i32, []const u8).New(env, "argumentsLength", argumentsLength); +} diff --git a/node-test/napi-compat-mode/src/lib.zig b/node-test/napi-compat-mode/src/lib.zig new file mode 100644 index 0000000..9205806 --- /dev/null +++ b/node-test/napi-compat-mode/src/lib.zig @@ -0,0 +1,85 @@ +const napi = @import("napi"); + +const napi_version = @import("napi_version.zig"); +const array = @import("array.zig"); +const arraybuffer = @import("arraybuffer.zig"); +const buffer = @import("buffer.zig"); +const either = @import("either.zig"); +const function = @import("function.zig"); +const string = @import("string.zig"); +const throw = @import("throw.zig"); +const deferred = @import("napi4/deferred.zig"); +const threadsafe_function = @import("napi4/threadsafe_function.zig"); +const date = @import("napi5/date.zig"); +const bigint = @import("napi6/bigint.zig"); +const detachable_arraybuffer = @import("napi7/arraybuffer.zig"); +const object = @import("napi8/object.zig"); + +pub const getNapiVersion = napi_version.getNapiVersion; + +pub const testCreateArray = array.testCreateArray; +pub const testCreateArrayWithLength = array.testCreateArrayWithLength; +pub const testSetElement = array.testSetElement; +pub const testHasElement = array.testHasElement; +pub const testDeleteElement = array.testDeleteElement; + +pub const getArraybufferLength = arraybuffer.getArraybufferLength; +pub const createEmptyArraybufferFromNew = arraybuffer.createEmptyArraybufferFromNew; +pub const createEmptyArraybufferFromData = arraybuffer.createEmptyArraybufferFromData; +pub const createEmptyExternalArraybuffer = arraybuffer.createEmptyExternalArraybuffer; +pub const createExternalArraybuffer = arraybuffer.createExternalArraybuffer; +pub const mutateUint8Array = arraybuffer.mutateUint8Array; +pub const mutateUint16Array = arraybuffer.mutateUint16Array; +pub const mutateInt16Array = arraybuffer.mutateInt16Array; +pub const mutateFloat32Array = arraybuffer.mutateFloat32Array; +pub const mutateFloat64Array = arraybuffer.mutateFloat64Array; + +pub const getBufferLength = buffer.getBufferLength; +pub const bufferToString = buffer.bufferToString; +pub const copyBuffer = buffer.copyBuffer; +pub const createBorrowedBufferWithNoopFinalize = buffer.createBorrowedBufferWithNoopFinalize; +pub const createBorrowedBufferWithFinalize = buffer.createBorrowedBufferWithFinalize; +pub const createEmptyBuffer = buffer.createEmptyBuffer; +pub const createEmptyBufferFromNew = buffer.createEmptyBufferFromNew; +pub const createEmptyExternalBuffer = buffer.createEmptyExternalBuffer; +pub const mutateBuffer = buffer.mutateBuffer; + +pub const eitherNumberString = either.eitherNumberString; +pub const dynamicArgumentLength = either.dynamicArgumentLength; + +pub const testCallFunction = function.testCallFunction; +pub const testCallFunctionWithRefArguments = function.testCallFunctionWithRefArguments; +pub const testCallFunctionError = function.testCallFunctionError; +pub const testCreateFunctionFromClosure = function.testCreateFunctionFromClosure; + +pub const concatString = string.concatString; +pub const concatUTF16String = string.concatUTF16String; +pub const concatLatin1String = string.concatLatin1String; +pub const createLatin1 = string.createLatin1; + +pub const testThrow = throw.testThrow; +pub const testThrowWithReason = throw.testThrowWithReason; +pub const testThrowWithPanic = throw.testThrowWithPanic; + +pub const doubleAsync = deferred.doubleAsync; +pub const callThreadsafeFunction = threadsafe_function.callThreadsafeFunction; + +pub const isDate = date.isDate; +pub const createDate = date.createDate; +pub const getDateValue = date.getDateValue; + +pub const createBigInt = bigint.createBigInt; +pub const makeBigInt = bigint.makeBigInt; +pub const bigintToI64 = bigint.bigintToI64; +pub const bigintAdd = bigint.bigintAdd; +pub const mutateI64Array = bigint.mutateI64Array; + +pub const detachArrayBuffer = detachable_arraybuffer.detachArrayBuffer; +pub const isDetachedArrayBuffer = detachable_arraybuffer.isDetachedArrayBuffer; + +pub const freezeObject = object.freezeObject; +pub const sealObject = object.sealObject; + +comptime { + napi.NODE_API_MODULE("compat_mode", @This()); +} diff --git a/node-test/napi-compat-mode/src/napi4/deferred.zig b/node-test/napi-compat-mode/src/napi4/deferred.zig new file mode 100644 index 0000000..b4355bf --- /dev/null +++ b/node-test/napi-compat-mode/src/napi4/deferred.zig @@ -0,0 +1,9 @@ +const napi = @import("napi"); + +fn doubleExecute(input: i32) i32 { + return input * 2; +} + +pub fn doubleAsync(value: i32) napi.Async(i32, .single) { + return napi.Async(i32, .single).from(value, doubleExecute); +} diff --git a/node-test/napi-compat-mode/src/napi4/threadsafe_function.zig b/node-test/napi-compat-mode/src/napi4/threadsafe_function.zig new file mode 100644 index 0000000..c6e1442 --- /dev/null +++ b/node-test/napi-compat-mode/src/napi4/threadsafe_function.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const napi = @import("napi"); + +const TsfnArgs = struct { i32, i32 }; +const TsfnReturn = i32; + +fn executeThreadSafeFunction(tsfn: *napi.ThreadSafeFunction(TsfnArgs, TsfnReturn, true, 0)) void { + defer tsfn.release(.Release) catch {}; + tsfn.Ok(.{ 1, 2 }, .NonBlocking) catch {}; +} + +pub fn callThreadsafeFunction(tsfn: *napi.ThreadSafeFunction(TsfnArgs, TsfnReturn, true, 0)) !void { + try tsfn.acquire(); + const worker = try std.Thread.spawn(.{}, executeThreadSafeFunction, .{tsfn}); + worker.detach(); + + try tsfn.release(.Release); +} diff --git a/node-test/napi-compat-mode/src/napi5/date.zig b/node-test/napi-compat-mode/src/napi5/date.zig new file mode 100644 index 0000000..d3b2784 --- /dev/null +++ b/node-test/napi-compat-mode/src/napi5/date.zig @@ -0,0 +1,32 @@ +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +const DateCandidate = union(enum) { + number: i32, + object: napi.Object, +}; + +pub fn isDate(value: DateCandidate) bool { + return switch (value) { + .number => false, + .object => |object| blk: { + var result = false; + _ = c.napi_is_date(object.env, object.raw, &result); + break :blk result; + }, + }; +} + +pub fn createDate(env: napi.Env, value: f64) !c.napi_value { + var raw: c.napi_value = undefined; + const status = c.napi_create_date(env.raw, value, &raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return raw; +} + +pub fn getDateValue(value: napi.Object) !f64 { + var result: f64 = 0; + const status = c.napi_get_date_value(value.env, value.raw, &result); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return result; +} diff --git a/node-test/napi-compat-mode/src/napi6/bigint.zig b/node-test/napi-compat-mode/src/napi6/bigint.zig new file mode 100644 index 0000000..3a122bb --- /dev/null +++ b/node-test/napi-compat-mode/src/napi6/bigint.zig @@ -0,0 +1,26 @@ +const std = @import("std"); +const napi = @import("napi"); + +pub fn createBigInt(env: napi.Env) napi.BigInt { + return napi.BigInt.New(env, @as(i128, 9007199254740993)); +} + +pub fn makeBigInt(env: napi.Env) napi.BigInt { + return createBigInt(env); +} + +pub fn bigintToI64(value: napi.BigInt) i64 { + return napi.BigInt.from_napi_value(value.env, value.raw, i64); +} + +pub fn bigintAdd(env: napi.Env, left: napi.BigInt, right: napi.BigInt) napi.BigInt { + const left_value = napi.BigInt.from_napi_value(left.env, left.raw, i64); + const right_value = napi.BigInt.from_napi_value(right.env, right.raw, i64); + return napi.BigInt.New(env, @as(i128, left_value + right_value)); +} + +pub fn mutateI64Array(values: napi.BigInt64Array) void { + if (values.length() > 0) { + values.asSlice()[0] = std.math.maxInt(i64); + } +} diff --git a/node-test/napi-compat-mode/src/napi7/arraybuffer.zig b/node-test/napi-compat-mode/src/napi7/arraybuffer.zig new file mode 100644 index 0000000..a02649c --- /dev/null +++ b/node-test/napi-compat-mode/src/napi7/arraybuffer.zig @@ -0,0 +1,14 @@ +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +pub fn detachArrayBuffer(buffer: napi.ArrayBuffer) !void { + const status = c.napi_detach_arraybuffer(buffer.env, buffer.raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); +} + +pub fn isDetachedArrayBuffer(buffer: napi.ArrayBuffer) !bool { + var result = false; + const status = c.napi_is_detached_arraybuffer(buffer.env, buffer.raw, &result); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return result; +} diff --git a/node-test/napi-compat-mode/src/napi8/object.zig b/node-test/napi-compat-mode/src/napi8/object.zig new file mode 100644 index 0000000..88a9d68 --- /dev/null +++ b/node-test/napi-compat-mode/src/napi8/object.zig @@ -0,0 +1,14 @@ +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +pub fn freezeObject(object: napi.Object) !c.napi_value { + const status = c.napi_object_freeze(object.env, object.raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return object.raw; +} + +pub fn sealObject(object: napi.Object) !c.napi_value { + const status = c.napi_object_seal(object.env, object.raw); + if (status != c.napi_ok) return napi.Error.fromStatus(napi.Status.New(status)); + return object.raw; +} diff --git a/node-test/napi-compat-mode/src/napi_version.zig b/node-test/napi-compat-mode/src/napi_version.zig new file mode 100644 index 0000000..8d8534d --- /dev/null +++ b/node-test/napi-compat-mode/src/napi_version.zig @@ -0,0 +1,8 @@ +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +pub fn getNapiVersion(env: napi.Env) u32 { + var result: u32 = 0; + _ = c.napi_get_version(env.raw, &result); + return result; +} diff --git a/node-test/napi-compat-mode/src/string.zig b/node-test/napi-compat-mode/src/string.zig new file mode 100644 index 0000000..7752d28 --- /dev/null +++ b/node-test/napi-compat-mode/src/string.zig @@ -0,0 +1,26 @@ +const napi = @import("napi"); + +fn concatWithRustSuffix(value: []const u8) ![]u8 { + const suffix = " + Rust 🦀 string!"; + const allocator = napi.globalAllocator(); + const out = try allocator.alloc(u8, value.len + suffix.len); + @memcpy(out[0..value.len], value); + @memcpy(out[value.len..], suffix); + return out; +} + +pub fn concatString(value: []const u8) ![]u8 { + return try concatWithRustSuffix(value); +} + +pub fn concatUTF16String(value: []const u8) ![]u8 { + return try concatWithRustSuffix(value); +} + +pub fn concatLatin1String(value: []const u8) ![]u8 { + return try concatWithRustSuffix(value); +} + +pub fn createLatin1() []const u8 { + return "©¿"; +} diff --git a/node-test/napi-compat-mode/src/throw.zig b/node-test/napi-compat-mode/src/throw.zig new file mode 100644 index 0000000..83c06d3 --- /dev/null +++ b/node-test/napi-compat-mode/src/throw.zig @@ -0,0 +1,13 @@ +const napi = @import("napi"); + +pub fn testThrow() !void { + return napi.Error.fromReason("native error"); +} + +pub fn testThrowWithReason(reason: []const u8) !void { + return napi.Error.fromReason(reason); +} + +pub fn testThrowWithPanic() !void { + return napi.Error.fromReason("panic from native"); +} diff --git a/node-test/napi/__tests__/binding.js b/node-test/napi/__tests__/binding.js new file mode 100644 index 0000000..0af386d --- /dev/null +++ b/node-test/napi/__tests__/binding.js @@ -0,0 +1,3 @@ +const path = require("path"); + +module.exports = require(path.join(__dirname, "..", "..", "load-addon"))("example"); diff --git a/node-test/napi/__tests__/error-msg.spec.js b/node-test/napi/__tests__/error-msg.spec.js new file mode 100644 index 0000000..849183d --- /dev/null +++ b/node-test/napi/__tests__/error-msg.spec.js @@ -0,0 +1,7 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("Function message", (t) => { + const error = t.throws(bindings.throwError); + t.regex(error.message, /native error/); +}); diff --git a/node-test/napi/__tests__/strict.spec.js b/node-test/napi/__tests__/strict.spec.js new file mode 100644 index 0000000..99db0aa --- /dev/null +++ b/node-test/napi/__tests__/strict.spec.js @@ -0,0 +1,66 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("should validate array", (t) => { + t.is(bindings.validateArray([1, 2, 3]), 3); + t.throws(() => bindings.validateArray(1)); +}); + +test("should validate arraybuffer", (t) => { + t.is(bindings.validateTypedArray(new Uint8Array([1, 2, 3])), 3); + t.throws(() => bindings.validateTypedArray(1)); +}); + +test("should validate BigInt", (t) => { + const fx = 1024n; + t.is(bindings.validateBigint(fx), fx); + t.throws(() => bindings.validateBigint(1)); +}); + +test("should validate buffer", (t) => { + t.is(bindings.validateBuffer(Buffer.from("hello")), 5); + t.throws(() => bindings.validateBuffer(2)); +}); + +test("should validate boolean value", (t) => { + t.is(bindings.validateBoolean(true), false); + t.is(bindings.validateBoolean(false), true); + t.throws(() => bindings.validateBoolean(1)); +}); + +test("should validate function", (t) => { + t.is( + bindings.validateFunction(() => 4), + 4, + ); + t.throws(() => bindings.validateFunction(2)); +}); + +test("should validate string", (t) => { + t.is(bindings.validateString("hello"), "hello!"); + t.throws(() => bindings.validateString(1)); +}); + +test("should validate null", (t) => { + t.notThrows(() => bindings.validateNull(null)); + t.throws(() => bindings.validateNull(1)); +}); + +test("should validate undefined", (t) => { + t.notThrows(() => bindings.validateUndefined(undefined)); + t.throws(() => bindings.validateUndefined(1)); +}); + +test("should validate enum", (t) => { + t.is(bindings.validateEnum(bindings.KindInValidate.Cat), bindings.KindInValidate.Cat); + t.throws(() => bindings.validateEnum("3")); + t.is(bindings.validateStringEnum(bindings.StatusInValidate.Poll), "Poll"); + t.throws(() => bindings.validateStringEnum(1)); +}); + +test("should validate Option", (t) => { + t.is(bindings.validateOptional(null, null), false); + t.is(bindings.validateOptional(null, false), false); + t.is(bindings.validateOptional("1", false), true); + t.is(bindings.validateOptional(null, true), true); +}); diff --git a/node-test/napi/__tests__/values.spec.js b/node-test/napi/__tests__/values.spec.js new file mode 100644 index 0000000..6187954 --- /dev/null +++ b/node-test/napi/__tests__/values.spec.js @@ -0,0 +1,205 @@ +const test = require("ava"); +const bindings = require("./binding"); + +test("export const", (t) => { + t.is(bindings.DEFAULT_COST, 12); +}); + +test("number", (t) => { + t.is(bindings.add(1, 2), 3); + t.is(bindings.fibonacci(5), 5); + t.throws(() => bindings.fibonacci("")); +}); + +test("string", (t) => { + t.true(bindings.contains("hello", "ell")); + t.false(bindings.contains("John", "jn")); + + t.is(bindings.concatStr("æ¶½¾DEL"), "æ¶½¾DEL + Rust 🦀 string!"); + t.is(bindings.concatLatin1("æ¶½¾DEL"), "æ¶½¾DEL + Rust 🦀 string!"); + t.is( + bindings.concatUtf16("JavaScript 🌳 你好 napi"), + "JavaScript 🌳 你好 napi + Rust 🦀 string!", + ); + t.is(bindings.roundtripStr("what up?!\0after the NULL"), "what up?!\0after the NULL"); +}); + +test("array", (t) => { + t.deepEqual(bindings.getNums(), [1, 1, 2, 3, 5, 8]); + t.deepEqual(bindings.getWords(), ["foo", "bar"]); + t.deepEqual(bindings.getTuple([1, "test", 2]), 3); + t.is(bindings.sumNums([1, 2, 3, 4, 5]), 15); + t.deepEqual(bindings.getNumArr(), [1, 2]); + t.deepEqual(bindings.getNestedNumArr(), [[[1]], [[1]]]); +}); + +test("map", (t) => { + t.deepEqual(bindings.getMapping(), { a: 101, b: 102, "\0c": 103 }); + t.is(bindings.sumMapping({ a: 101, b: 102, "\0c": 103 }), 306); + t.deepEqual(bindings.indexmapPassthrough({ a: 101, b: 102, "\0c": 103 }), { + a: 101, + b: 102, + "\0c": 103, + }); +}); + +test("enum", (t) => { + t.deepEqual([bindings.Kind.Dog, bindings.Kind.Cat, bindings.Kind.Duck], [0, 1, 2]); + t.is(bindings.enumToI32(bindings.CustomNumEnum.Eight), 8); +}); + +test("function call", (t) => { + t.is( + bindings.call0((...args) => { + t.is(args.length, 0); + return 42; + }), + 42, + ); + t.is( + bindings.call1((a) => a + 10, 42), + 52, + ); + t.is( + bindings.call2((a, b) => a + b, 42, 10), + 52, + ); + t.is( + bindings.callFunction(() => 42), + 42, + ); + t.is( + bindings.callFunctionWithArg((a, b) => a + b, 42, 10), + 52, + ); + + const fn = bindings.createFunction(); + t.is(fn(42), 242); +}); + +test("object", (t) => { + t.deepEqual(bindings.createObj(), { x: 1, y: 2 }); + t.deepEqual(bindings.translatePoint({ x: 1, y: 2 }, 3, 4), { x: 4, y: 6 }); + t.deepEqual(bindings.listObjKeys({ z: 1, a: 2 }).sort(), ["a", "z"]); +}); + +test("global, undefined, null and symbol", (t) => { + t.is(bindings.getGlobal(), globalThis); + t.is(bindings.getUndefined(), undefined); + t.is(bindings.returnUndefined(), undefined); + t.is(bindings.getNull(), null); + t.is(bindings.returnNull(), null); + + const symbol = bindings.createSymbol("foo"); + t.is(typeof symbol, "symbol"); + t.is(symbol.description, "foo"); + + const obj = {}; + t.is(bindings.setSymbolInObj(obj), obj); + const symbols = Object.getOwnPropertySymbols(obj); + t.is(symbols.length, 1); + t.is(symbols[0].description, "native"); + t.is(obj[symbols[0]], "symbol-value"); +}); + +test("Result", (t) => { + t.throws(bindings.throwError); + t.throws(bindings.throwTypeError, { instanceOf: TypeError }); + t.throws(bindings.throwRangeError, { instanceOf: RangeError }); +}); + +test("buffer", (t) => { + const buffer = Buffer.from("hello"); + t.deepEqual(bindings.getBuffer(), Buffer.from("hello world")); + t.is(bindings.getEmptyBuffer().length, 0); + t.is(bindings.getEmptyBufferFromNew().length, 0); + t.is(bindings.getEmptyExternalBuffer().length, 0); + t.deepEqual(bindings.appendBuffer(buffer), Buffer.from("hello world")); + t.is(bindings.bufferPassThrough(buffer), buffer); + t.deepEqual(bindings.getBufferSlice(Buffer.from("abcdef"), 1, 4), Buffer.from("bcd")); + t.deepEqual(bindings.createExternalBufferSlice(), Buffer.from("external")); + t.deepEqual(bindings.createBufferSliceFromCopiedData(), Buffer.from("copied")); +}); + +test("ArrayBuffer", (t) => { + const buffer = new ArrayBuffer(4); + t.is(bindings.createArraybuffer(8).byteLength, 8); + t.is(bindings.createArraybuffer(0).byteLength, 0); + t.is(bindings.createEmptyArraybuffer().byteLength, 0); + t.is(bindings.acceptArraybuffer(buffer), 4); + t.is(bindings.arrayBufferPassThrough(buffer), buffer); + + const mutable = new Uint8Array([1, 2, 3]).buffer; + bindings.mutateArraybuffer(mutable); + t.deepEqual(Array.from(new Uint8Array(mutable)), [2, 3, 4]); + + t.deepEqual(Array.from(new Uint8Array(bindings.arrayBufferFromData())), [1, 2, 3, 4]); + t.is(bindings.arrayBufferFromEmptyData().byteLength, 0); + t.deepEqual(Array.from(new Uint8Array(bindings.arrayBufferFromExternal())), [5, 6, 7, 8]); + t.is(bindings.arrayBufferFromEmptyExternal().byteLength, 0); +}); + +test("TypedArray", (t) => { + t.true(bindings.getEmptyTypedArray() instanceof Uint8Array); + t.is(bindings.getEmptyTypedArray().length, 0); + + t.deepEqual(bindings.u8ArrayToArray(new Uint8Array([1, 2, 3])), [1, 2, 3]); + t.deepEqual(bindings.i8ArrayToArray(new Int8Array([-1, 2])), [-1, 2]); + t.deepEqual(bindings.u16ArrayToArray(new Uint16Array([1, 2])), [1, 2]); + t.deepEqual(bindings.i16ArrayToArray(new Int16Array([-1, 2])), [-1, 2]); + t.deepEqual(bindings.u32ArrayToArray(new Uint32Array([1, 2])), [1, 2]); + t.deepEqual(bindings.i32ArrayToArray(new Int32Array([-1, 2])), [-1, 2]); + t.deepEqual(bindings.f32ArrayToArray(new Float32Array([1.5, 2.5])), [1.5, 2.5]); + t.deepEqual(bindings.f64ArrayToArray(new Float64Array([1.5, 2.5])), [1.5, 2.5]); + t.deepEqual(bindings.i64ArrayToArray(new BigInt64Array([1n, -2n])), [1, -2]); + t.deepEqual(bindings.u64ArrayToArray(new BigUint64Array([1n, 2n])), [1, 2]); + + t.is(bindings.acceptSlice(new Int32Array([1, 2, 3])), 6); + t.is(bindings.acceptUint8ClampedSlice(new Uint8ClampedArray([1, 2, 3])), 6); + t.deepEqual(Array.from(bindings.convertU32Array(new Uint32Array([3, 4]))), [3, 4]); + t.deepEqual(Array.from(bindings.createExternalTypedArray()), [1, 2, 3]); + t.deepEqual(Array.from(bindings.createUint8ClampedArrayFromData()), [1, 2, 255]); + t.deepEqual(Array.from(bindings.uint8ArrayFromData()), [1, 2, 3, 4]); + t.deepEqual(Array.from(bindings.uint8ArrayFromExternal()), [5, 6, 7, 8]); + + const mutable = new Uint8Array([1, 2, 3]); + bindings.mutateTypedArray(mutable); + t.deepEqual(Array.from(mutable), [2, 3, 4]); +}); + +test("DataView", (t) => { + const created = bindings.createDataView(); + t.true(created instanceof DataView); + t.is(bindings.readDataView(created), 0x1234); + + const view = new DataView(new ArrayBuffer(4)); + bindings.mutateDataView(view); + t.is(view.getUint16(0, true), 0x1234); +}); + +test("async", async (t) => { + t.is(await bindings.asyncPlus100(23), 123); + t.is(await bindings.asyncTaskOptionalReturn(true), 42); + t.is(await bindings.asyncTaskOptionalReturn(false), undefined); + t.deepEqual(await bindings.asyncResolveArray(4), [0, 1, 2, 3]); +}); + +const BigIntTest = typeof BigInt !== "undefined" ? test : test.skip; + +BigIntTest("bigint", (t) => { + t.is(bindings.createBigInt(), -3689348814741910323300n); + t.is(bindings.createBigIntI64(), 100n); + t.is(bindings.bigintAdd(20n, 22n), 42n); + t.is(bindings.bigintGetU64AsString(0n), "0"); + t.is(bindings.bigintFromI64(), 100n); + t.is(bindings.bigintFromI128(), -100n); +}); + +test("either", (t) => { + t.is(bindings.eitherStringOrNumber(1), 101); + t.is(bindings.eitherStringOrNumber("napi"), "napi"); + t.is(bindings.returnEither(true), "napi"); + t.is(bindings.returnEither(false), 42); + t.is(bindings.eitherFromOption("zig"), "zig"); + t.is(bindings.eitherFromOption(null), 0); +}); diff --git a/node-test/napi/__tests__/worker-thread.spec.js b/node-test/napi/__tests__/worker-thread.spec.js new file mode 100644 index 0000000..9ca6c84 --- /dev/null +++ b/node-test/napi/__tests__/worker-thread.spec.js @@ -0,0 +1,14 @@ +const path = require("path"); +const { Worker } = require("worker_threads"); +const test = require("ava"); + +test("should be able to require in worker thread", async (t) => { + const worker = new Worker(path.join(__dirname, "worker.js")); + const message = await new Promise((resolve, reject) => { + worker.once("message", resolve); + worker.once("error", reject); + }); + + t.is(message.sum, 42); + t.is(`${message.napiVersion}`, process.versions.napi); +}); diff --git a/node-test/napi/__tests__/worker.js b/node-test/napi/__tests__/worker.js new file mode 100644 index 0000000..9de1bdb --- /dev/null +++ b/node-test/napi/__tests__/worker.js @@ -0,0 +1,7 @@ +const { parentPort } = require("worker_threads"); +const bindings = require("./binding"); + +parentPort.postMessage({ + sum: bindings.add(20, 22), + napiVersion: bindings.getNapiVersion(), +}); diff --git a/node-test/napi/src/lib.zig b/node-test/napi/src/lib.zig new file mode 100644 index 0000000..1a2b452 --- /dev/null +++ b/node-test/napi/src/lib.zig @@ -0,0 +1,119 @@ +const napi = @import("napi"); + +const values = @import("values.zig"); +const strict = @import("strict.zig"); +const threadsafe_function = @import("threadsafe_function.zig"); + +pub const DEFAULT_COST = values.DEFAULT_COST; +pub const Kind = values.Kind; +pub const CustomNumEnum = values.CustomNumEnum; +pub const KindInValidate = strict.KindInValidate; +pub const StatusInValidate = strict.StatusInValidate; + +pub const getNapiVersion = values.getNapiVersion; +pub const add = values.add; +pub const fibonacci = values.fibonacci; +pub const contains = values.contains; +pub const concatStr = values.concatStr; +pub const concatLatin1 = values.concatLatin1; +pub const concatUtf16 = values.concatUtf16; +pub const roundtripStr = values.roundtripStr; +pub const getNums = values.getNums; +pub const getWords = values.getWords; +pub const sumNums = values.sumNums; +pub const getTuple = values.getTuple; +pub const getNumArr = values.getNumArr; +pub const getNestedNumArr = values.getNestedNumArr; +pub const translatePoint = values.translatePoint; +pub const getMapping = values.getMapping; +pub const sumMapping = values.sumMapping; +pub const indexmapPassthrough = values.indexmapPassthrough; +pub const enumToI32 = values.enumToI32; +pub const call0 = values.call0; +pub const call1 = values.call1; +pub const call2 = values.call2; +pub const callFunction = values.callFunction; +pub const callFunctionWithArg = values.callFunctionWithArg; +pub const createFunction = values.createFunction; +pub const createObj = values.createObj; +pub const listObjKeys = values.listObjKeys; +pub const getGlobal = values.getGlobal; +pub const getUndefined = values.getUndefined; +pub const getNull = values.getNull; +pub const returnUndefined = values.returnUndefined; +pub const returnNull = values.returnNull; +pub const createSymbol = values.createSymbol; +pub const setSymbolInObj = values.setSymbolInObj; +pub const throwError = values.throwError; +pub const throwTypeError = values.throwTypeError; +pub const throwRangeError = values.throwRangeError; +pub const getBuffer = values.getBuffer; +pub const getEmptyBuffer = values.getEmptyBuffer; +pub const getEmptyBufferFromNew = values.getEmptyBufferFromNew; +pub const getEmptyExternalBuffer = values.getEmptyExternalBuffer; +pub const appendBuffer = values.appendBuffer; +pub const bufferPassThrough = values.bufferPassThrough; +pub const createArraybuffer = values.createArraybuffer; +pub const createEmptyArraybuffer = values.createEmptyArraybuffer; +pub const acceptArraybuffer = values.acceptArraybuffer; +pub const arrayBufferPassThrough = values.arrayBufferPassThrough; +pub const getBufferSlice = values.getBufferSlice; +pub const createExternalBufferSlice = values.createExternalBufferSlice; +pub const createBufferSliceFromCopiedData = values.createBufferSliceFromCopiedData; +pub const getEmptyTypedArray = values.getEmptyTypedArray; +pub const u8ArrayToArray = values.u8ArrayToArray; +pub const i8ArrayToArray = values.i8ArrayToArray; +pub const u16ArrayToArray = values.u16ArrayToArray; +pub const i16ArrayToArray = values.i16ArrayToArray; +pub const u32ArrayToArray = values.u32ArrayToArray; +pub const i32ArrayToArray = values.i32ArrayToArray; +pub const f32ArrayToArray = values.f32ArrayToArray; +pub const f64ArrayToArray = values.f64ArrayToArray; +pub const i64ArrayToArray = values.i64ArrayToArray; +pub const u64ArrayToArray = values.u64ArrayToArray; +pub const acceptSlice = values.acceptSlice; +pub const acceptUint8ClampedSlice = values.acceptUint8ClampedSlice; +pub const convertU32Array = values.convertU32Array; +pub const createExternalTypedArray = values.createExternalTypedArray; +pub const mutateTypedArray = values.mutateTypedArray; +pub const mutateArraybuffer = values.mutateArraybuffer; +pub const createUint8ClampedArrayFromData = values.createUint8ClampedArrayFromData; +pub const arrayBufferFromData = values.arrayBufferFromData; +pub const arrayBufferFromEmptyData = values.arrayBufferFromEmptyData; +pub const arrayBufferFromExternal = values.arrayBufferFromExternal; +pub const arrayBufferFromEmptyExternal = values.arrayBufferFromEmptyExternal; +pub const uint8ArrayFromData = values.uint8ArrayFromData; +pub const uint8ArrayFromExternal = values.uint8ArrayFromExternal; +pub const createDataView = values.createDataView; +pub const readDataView = values.readDataView; +pub const mutateDataView = values.mutateDataView; +pub const asyncPlus100 = values.asyncPlus100; +pub const asyncTaskOptionalReturn = values.asyncTaskOptionalReturn; +pub const asyncResolveArray = values.asyncResolveArray; +pub const createBigInt = values.createBigInt; +pub const createBigIntI64 = values.createBigIntI64; +pub const bigintAdd = values.bigintAdd; +pub const bigintGetU64AsString = values.bigintGetU64AsString; +pub const bigintFromI64 = values.bigintFromI64; +pub const bigintFromI128 = values.bigintFromI128; +pub const eitherStringOrNumber = values.eitherStringOrNumber; +pub const returnEither = values.returnEither; +pub const eitherFromOption = values.eitherFromOption; +pub const callThreadsafeFunction = threadsafe_function.callThreadsafeFunction; + +pub const validateArray = strict.validateArray; +pub const validateTypedArray = strict.validateTypedArray; +pub const validateBuffer = strict.validateBuffer; +pub const validateBigint = strict.validateBigint; +pub const validateBoolean = strict.validateBoolean; +pub const validateFunction = strict.validateFunction; +pub const validateString = strict.validateString; +pub const validateNull = strict.validateNull; +pub const validateUndefined = strict.validateUndefined; +pub const validateEnum = strict.validateEnum; +pub const validateStringEnum = strict.validateStringEnum; +pub const validateOptional = strict.validateOptional; + +comptime { + napi.NODE_API_MODULE("example", @This()); +} diff --git a/node-test/napi/src/strict.zig b/node-test/napi/src/strict.zig new file mode 100644 index 0000000..c61234c --- /dev/null +++ b/node-test/napi/src/strict.zig @@ -0,0 +1,107 @@ +const napi = @import("napi"); + +const ArrayInput = union(enum) { + value: []i32, +}; + +const TypedArrayInput = union(enum) { + value: napi.Uint8Array, +}; + +const BufferInput = union(enum) { + value: napi.Buffer, +}; + +const BigIntInput = union(enum) { + value: napi.BigInt, +}; + +const BooleanInput = union(enum) { + value: bool, +}; + +const FunctionInput = union(enum) { + value: napi.Function(struct {}, i32), +}; + +const StringInput = union(enum) { + value: []const u8, +}; + +const NullInput = union(enum) { + value: napi.Null, +}; + +const UndefinedInput = union(enum) { + value: napi.Undefined, +}; + +pub const KindInValidate = enum(u8) { + Dog = 1, + Cat = 2, +}; + +pub const StatusInValidate = enum { + Ready, + Poll, + + pub const napi_string_enum = true; +}; + +const KindInput = union(enum) { + value: KindInValidate, +}; + +const StatusInput = union(enum) { + value: StatusInValidate, +}; + +pub fn validateArray(input: ArrayInput) usize { + return input.value.len; +} + +pub fn validateTypedArray(input: TypedArrayInput) usize { + return input.value.length(); +} + +pub fn validateBuffer(input: BufferInput) usize { + return input.value.length(); +} + +pub fn validateBigint(input: BigIntInput) napi.BigInt { + return input.value; +} + +pub fn validateBoolean(input: BooleanInput) bool { + return !input.value; +} + +pub fn validateFunction(input: FunctionInput) !i32 { + return try input.value.Call(.{}); +} + +pub fn validateString(input: StringInput) ![]u8 { + const allocator = napi.globalAllocator(); + const suffix = "!"; + const out = try allocator.alloc(u8, input.value.len + suffix.len); + @memcpy(out[0..input.value.len], input.value); + @memcpy(out[input.value.len..], suffix); + return out; +} + +pub fn validateNull(_: NullInput) void {} + +pub fn validateUndefined(_: UndefinedInput) void {} + +pub fn validateEnum(input: KindInput) KindInValidate { + return input.value; +} + +pub fn validateStringEnum(input: StatusInput) StatusInValidate { + return input.value; +} + +pub fn validateOptional(value: ?[]const u8, default_value: ?bool) bool { + if (value != null) return true; + return default_value orelse false; +} diff --git a/node-test/napi/src/threadsafe_function.zig b/node-test/napi/src/threadsafe_function.zig new file mode 100644 index 0000000..d8838a2 --- /dev/null +++ b/node-test/napi/src/threadsafe_function.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const napi = @import("napi"); + +const TsfnArgs = struct { i32, i32 }; +const TsfnReturn = i32; + +fn executeThreadSafeFunction(tsfn: *napi.ThreadSafeFunction(TsfnArgs, TsfnReturn, true, 0)) void { + defer tsfn.release(.Release) catch {}; + tsfn.Ok(.{ 20, 22 }, .NonBlocking) catch {}; +} + +pub fn callThreadsafeFunction(tsfn: *napi.ThreadSafeFunction(TsfnArgs, TsfnReturn, true, 0)) !void { + try tsfn.acquire(); + const worker = try std.Thread.spawn(.{}, executeThreadSafeFunction, .{tsfn}); + worker.detach(); + + try tsfn.release(.Release); +} diff --git a/node-test/napi/src/values.zig b/node-test/napi/src/values.zig new file mode 100644 index 0000000..ae929a0 --- /dev/null +++ b/node-test/napi/src/values.zig @@ -0,0 +1,503 @@ +const std = @import("std"); +const napi = @import("napi"); +const c = napi.napi_sys.napi_sys; + +pub const DEFAULT_COST: i32 = 12; + +pub const Kind = enum(u8) { + Dog = 0, + Cat = 1, + Duck = 2, +}; + +pub const CustomNumEnum = enum(i32) { + One = 1, + Eight = 8, +}; + +pub const Point = struct { + x: i32, + y: i32, +}; + +const NumberOrString = union(enum) { + number: i32, + string: []const u8, +}; + +fn allocator() std.mem.Allocator { + return napi.globalAllocator(); +} + +fn check(status: c.napi_status) !void { + if (status != c.napi_ok) { + return napi.Error.fromStatus(napi.Status.New(status)); + } +} + +fn concatRustString(input: []const u8) ![]u8 { + return try std.fmt.allocPrint(allocator(), "{s} + Rust 🦀 string!", .{input}); +} + +fn plus100(input: i32) i32 { + return input + 100; +} + +fn optionalReturn(input: bool) ?i32 { + return if (input) 42 else null; +} + +fn resolveArray(count: u32) ![]i32 { + const out = try allocator().alloc(i32, count); + for (out, 0..) |*item, i| { + item.* = @intCast(i); + } + return out; +} + +fn add200(input: i32) i32 { + return input + 200; +} + +fn copyAs(comptime Dst: type, input: anytype) ![]Dst { + const out = try allocator().alloc(Dst, input.len); + for (input, 0..) |value, i| { + out[i] = switch (@typeInfo(Dst)) { + .int => @intCast(value), + .float => @floatCast(value), + else => @compileError("Unsupported copy destination type: " ++ @typeName(Dst)), + }; + } + return out; +} + +pub fn getNapiVersion(env: napi.Env) u32 { + var result: u32 = 0; + _ = c.napi_get_version(env.raw, &result); + return result; +} + +pub fn add(left: i32, right: i32) i32 { + return left + right; +} + +pub fn fibonacci(input: u32) u32 { + if (input <= 1) return input; + var previous: u32 = 0; + var current: u32 = 1; + for (2..input + 1) |_| { + const next = previous + current; + previous = current; + current = next; + } + return current; +} + +pub fn contains(input: []const u8, needle: []const u8) bool { + return std.mem.indexOf(u8, input, needle) != null; +} + +pub fn concatStr(input: []const u8) ![]u8 { + return try concatRustString(input); +} + +pub fn concatLatin1(input: []const u8) ![]u8 { + return try concatRustString(input); +} + +pub fn concatUtf16(input: []const u8) ![]u8 { + return try concatRustString(input); +} + +pub fn roundtripStr(input: []const u8) []const u8 { + return input; +} + +pub fn getNums() [6]i32 { + return .{ 1, 1, 2, 3, 5, 8 }; +} + +pub fn getWords() [2][]const u8 { + return .{ "foo", "bar" }; +} + +pub fn getTuple(input: struct { i32, []const u8, i32 }) i32 { + _ = @field(input, "1"); + return @field(input, "0") + @field(input, "2"); +} + +pub fn getNumArr() [2]i32 { + return .{ 1, 2 }; +} + +pub fn getNestedNumArr() [2][1][1]i32 { + return .{ .{.{1}}, .{.{1}} }; +} + +pub fn sumNums(values: []i32) i32 { + var total: i32 = 0; + for (values) |value| total += value; + return total; +} + +pub fn translatePoint(point: Point, dx: i32, dy: i32) Point { + return .{ + .x = point.x + dx, + .y = point.y + dy, + }; +} + +pub fn getMapping(env: napi.Env) !c.napi_value { + const object = try napi.Object.Create(env); + try object.Set("a", @as(i32, 101)); + try object.Set("b", @as(i32, 102)); + try object.Set("\x00c", @as(i32, 103)); + return object.raw; +} + +pub fn sumMapping(object: napi.Object) i32 { + return object.Get("a", i32) + object.Get("b", i32) + object.Get("\x00c", i32); +} + +pub fn indexmapPassthrough(object: napi.Object) c.napi_value { + return object.raw; +} + +pub fn enumToI32(value: CustomNumEnum) i32 { + return @intFromEnum(value); +} + +pub fn call0(callback: napi.Function(struct {}, i32)) !i32 { + return try callback.Call(.{}); +} + +pub fn call1(callback: napi.Function(i32, i32), value: i32) !i32 { + return try callback.Call(value); +} + +pub fn call2(callback: napi.Function(struct { i32, i32 }, i32), left: i32, right: i32) !i32 { + return try callback.Call(.{ left, right }); +} + +pub fn callFunction(callback: napi.Function(struct {}, i32)) !i32 { + return try callback.Call(.{}); +} + +pub fn callFunctionWithArg(callback: napi.Function(struct { i32, i32 }, i32), left: i32, right: i32) !i32 { + return try callback.Call(.{ left, right }); +} + +pub fn createFunction(env: napi.Env) !napi.Function(i32, i32) { + return try napi.Function(i32, i32).New(env, "add200", add200); +} + +pub fn createObj() Point { + return .{ .x = 1, .y = 2 }; +} + +pub fn listObjKeys(env: napi.Env, object: napi.Object) !c.napi_value { + var raw: c.napi_value = undefined; + try check(c.napi_get_property_names(env.raw, object.raw, &raw)); + return raw; +} + +pub fn getGlobal(env: napi.Env) !c.napi_value { + var raw: c.napi_value = undefined; + try check(c.napi_get_global(env.raw, &raw)); + return raw; +} + +pub fn getUndefined(env: napi.Env) napi.Undefined { + return env.getUndefined(); +} + +pub fn getNull(env: napi.Env) napi.Null { + return env.getNull(); +} + +pub fn returnUndefined(env: napi.Env) napi.Undefined { + return env.getUndefined(); +} + +pub fn returnNull(env: napi.Env) napi.Null { + return env.getNull(); +} + +pub fn createSymbol(env: napi.Env, description: []const u8) !c.napi_value { + const desc = napi.String.New(env, description); + var symbol: c.napi_value = undefined; + try check(c.napi_create_symbol(env.raw, desc.raw, &symbol)); + return symbol; +} + +pub fn setSymbolInObj(env: napi.Env, object: napi.Object) !c.napi_value { + const desc = napi.String.New(env, "native"); + var symbol: c.napi_value = undefined; + try check(c.napi_create_symbol(env.raw, desc.raw, &symbol)); + const value = napi.String.New(env, "symbol-value"); + try check(c.napi_set_property(env.raw, object.raw, symbol, value.raw)); + return object.raw; +} + +pub fn throwError() !void { + return napi.Error.fromReason("native error"); +} + +pub fn throwTypeError() !void { + return napi.Error.typeError("type error from native"); +} + +pub fn throwRangeError() !void { + return napi.Error.rangeError("range error from native"); +} + +pub fn getBuffer(env: napi.Env) !napi.Buffer { + return try napi.Buffer.copy(env, "hello world"[0..]); +} + +pub fn getEmptyBuffer(env: napi.Env) !napi.Buffer { + return try napi.Buffer.copy(env, &[_]u8{}); +} + +pub fn getEmptyBufferFromNew(env: napi.Env) !napi.Buffer { + return try napi.Buffer.New(env, 0); +} + +pub fn getEmptyExternalBuffer(env: napi.Env) !napi.Buffer { + const bytes = try allocator().alloc(u8, 0); + errdefer allocator().free(bytes); + return try napi.Buffer.from(env, bytes); +} + +pub fn appendBuffer(env: napi.Env, input: napi.Buffer) !napi.Buffer { + const suffix = " world"; + const input_slice = input.asConstSlice(); + const out = try allocator().alloc(u8, input_slice.len + suffix.len); + defer allocator().free(out); + @memcpy(out[0..input_slice.len], input_slice); + @memcpy(out[input_slice.len..], suffix); + return try napi.Buffer.copy(env, out); +} + +pub fn bufferPassThrough(input: napi.Buffer) c.napi_value { + return input.raw; +} + +pub fn createArraybuffer(env: napi.Env, len: u32) !napi.ArrayBuffer { + return try napi.ArrayBuffer.New(env, len); +} + +pub fn createEmptyArraybuffer(env: napi.Env) !napi.ArrayBuffer { + return try napi.ArrayBuffer.New(env, 0); +} + +pub fn acceptArraybuffer(input: napi.ArrayBuffer) usize { + return input.length(); +} + +pub fn arrayBufferPassThrough(input: napi.ArrayBuffer) c.napi_value { + return input.raw; +} + +pub fn getBufferSlice(env: napi.Env, input: napi.Buffer, start: u32, end: u32) !napi.Buffer { + const slice = input.asConstSlice(); + if (start > end or end > slice.len) { + return napi.Error.rangeError("buffer slice range is out of bounds"); + } + return try napi.Buffer.copy(env, slice[start..end]); +} + +pub fn createExternalBufferSlice(env: napi.Env) !napi.Buffer { + const bytes = try allocator().alloc(u8, 8); + errdefer allocator().free(bytes); + @memcpy(bytes, "external"); + return try napi.Buffer.from(env, bytes); +} + +pub fn createBufferSliceFromCopiedData(env: napi.Env) !napi.Buffer { + return try napi.Buffer.copy(env, "copied-data"[0..6]); +} + +pub fn getEmptyTypedArray(env: napi.Env) !napi.Uint8Array { + return try napi.Uint8Array.New(env, 0); +} + +pub fn u8ArrayToArray(input: napi.Uint8Array) ![]i32 { + return try copyAs(i32, input.asConstSlice()); +} + +pub fn i8ArrayToArray(input: napi.Int8Array) ![]i32 { + return try copyAs(i32, input.asConstSlice()); +} + +pub fn u16ArrayToArray(input: napi.Uint16Array) ![]i32 { + return try copyAs(i32, input.asConstSlice()); +} + +pub fn i16ArrayToArray(input: napi.Int16Array) ![]i32 { + return try copyAs(i32, input.asConstSlice()); +} + +pub fn u32ArrayToArray(input: napi.Uint32Array) ![]u32 { + return try copyAs(u32, input.asConstSlice()); +} + +pub fn i32ArrayToArray(input: napi.Int32Array) ![]i32 { + return try copyAs(i32, input.asConstSlice()); +} + +pub fn f32ArrayToArray(input: napi.Float32Array) ![]f64 { + return try copyAs(f64, input.asConstSlice()); +} + +pub fn f64ArrayToArray(input: napi.Float64Array) ![]f64 { + return try copyAs(f64, input.asConstSlice()); +} + +pub fn i64ArrayToArray(input: napi.BigInt64Array) ![]i64 { + return try copyAs(i64, input.asConstSlice()); +} + +pub fn u64ArrayToArray(input: napi.BigUint64Array) ![]u64 { + return try copyAs(u64, input.asConstSlice()); +} + +pub fn acceptSlice(values: []i32) i32 { + return sumNums(values); +} + +pub fn acceptUint8ClampedSlice(values: []i32) i32 { + return sumNums(values); +} + +pub fn convertU32Array(env: napi.Env, input: napi.Uint32Array) !napi.Uint32Array { + return try napi.Uint32Array.copy(env, input.asConstSlice()); +} + +pub fn createExternalTypedArray(env: napi.Env) !napi.Uint32Array { + return try napi.Uint32Array.copy(env, &[_]u32{ 1, 2, 3 }); +} + +pub fn mutateTypedArray(input: napi.Uint8Array) void { + for (input.asSlice()) |*value| { + value.* +%= 1; + } +} + +pub fn mutateArraybuffer(input: napi.ArrayBuffer) void { + for (input.asSlice()) |*value| { + value.* +%= 1; + } +} + +pub fn createUint8ClampedArrayFromData(env: napi.Env) !c.napi_value { + var arraybuffer = try napi.ArrayBuffer.New(env, 3); + @memcpy(arraybuffer.asSlice(), &[_]u8{ 1, 2, 255 }); + + var raw: c.napi_value = undefined; + try check(c.napi_create_typedarray(env.raw, c.napi_uint8_clamped_array, 3, arraybuffer.raw, 0, &raw)); + return raw; +} + +pub fn arrayBufferFromData(env: napi.Env) !napi.ArrayBuffer { + return try napi.ArrayBuffer.copy(env, &[_]u8{ 1, 2, 3, 4 }); +} + +pub fn arrayBufferFromEmptyData(env: napi.Env) !napi.ArrayBuffer { + return try napi.ArrayBuffer.copy(env, &[_]u8{}); +} + +pub fn arrayBufferFromExternal(env: napi.Env) !napi.ArrayBuffer { + const bytes = try allocator().alloc(u8, 4); + errdefer allocator().free(bytes); + @memcpy(bytes, &[_]u8{ 5, 6, 7, 8 }); + return try napi.ArrayBuffer.from(env, bytes); +} + +pub fn arrayBufferFromEmptyExternal(env: napi.Env) !napi.ArrayBuffer { + const bytes = try allocator().alloc(u8, 0); + errdefer allocator().free(bytes); + return try napi.ArrayBuffer.from(env, bytes); +} + +pub fn uint8ArrayFromData(env: napi.Env) !napi.Uint8Array { + return try napi.Uint8Array.copy(env, &[_]u8{ 1, 2, 3, 4 }); +} + +pub fn uint8ArrayFromExternal(env: napi.Env) !napi.Uint8Array { + return try napi.Uint8Array.copy(env, &[_]u8{ 5, 6, 7, 8 }); +} + +pub fn createDataView(env: napi.Env) !napi.DataView { + return try napi.DataView.copy(env, &[_]u8{ 0x34, 0x12, 0, 0 }); +} + +pub fn readDataView(input: napi.DataView) !i32 { + return try input.getUint16(0, true); +} + +pub fn mutateDataView(input: napi.DataView) !void { + try input.setUint16(0, 0x1234, true); +} + +pub fn asyncPlus100(value: i32) napi.Async(i32, .single) { + return napi.Async(i32, .single).from(value, plus100); +} + +pub fn asyncTaskOptionalReturn(value: bool) napi.Async(?i32, .single) { + return napi.Async(?i32, .single).from(value, optionalReturn); +} + +pub fn asyncResolveArray(count: u32) napi.Async([]i32, .single) { + return napi.Async([]i32, .single).from(count, resolveArray); +} + +pub fn createBigInt(env: napi.Env) napi.BigInt { + return napi.BigInt.New(env, @as(i128, -3689348814741910323300)); +} + +pub fn createBigIntI64(env: napi.Env) napi.BigInt { + return napi.BigInt.New(env, @as(i128, 100)); +} + +pub fn bigintAdd(env: napi.Env, left: napi.BigInt, right: napi.BigInt) napi.BigInt { + const left_value = napi.BigInt.from_napi_value(left.env, left.raw, i64); + const right_value = napi.BigInt.from_napi_value(right.env, right.raw, i64); + return napi.BigInt.New(env, @as(i128, left_value + right_value)); +} + +pub fn bigintGetU64AsString(value: napi.BigInt) ![]u8 { + const raw = napi.BigInt.from_napi_value(value.env, value.raw, u64); + return try std.fmt.allocPrint(allocator(), "{d}", .{raw}); +} + +pub fn bigintFromI64(env: napi.Env) napi.BigInt { + return napi.BigInt.New(env, @as(i128, 100)); +} + +pub fn bigintFromI128(env: napi.Env) napi.BigInt { + return napi.BigInt.New(env, @as(i128, -100)); +} + +pub fn eitherStringOrNumber(env: napi.Env, value: NumberOrString) !c.napi_value { + switch (value) { + .number => |number| { + var raw: c.napi_value = undefined; + try check(c.napi_create_int32(env.raw, number + 100, &raw)); + return raw; + }, + .string => |string| { + var raw: c.napi_value = undefined; + try check(c.napi_create_string_utf8(env.raw, string.ptr, string.len, &raw)); + return raw; + }, + } +} + +pub fn returnEither(value: bool) NumberOrString { + return if (value) .{ .string = "napi" } else .{ .number = 42 }; +} + +pub fn eitherFromOption(value: ?[]const u8) NumberOrString { + return if (value) |payload| .{ .string = payload } else .{ .number = 0 }; +} diff --git a/node-test/package.json b/node-test/package.json new file mode 100644 index 0000000..8148b40 --- /dev/null +++ b/node-test/package.json @@ -0,0 +1,22 @@ +{ + "name": "zig-napi-node-test", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "zig build --summary all", + "test": "ava --serial", + "test:compat": "ava --serial \"napi-compat-mode/__tests__/**/*.spec.js\"", + "test:napi": "ava --serial \"napi/__tests__/**/*.spec.js\"" + }, + "devDependencies": { + "ava": "3.15.0" + }, + "ava": { + "files": [ + "napi-compat-mode/__tests__/**/*.spec.js", + "napi/__tests__/**/*.spec.js" + ], + "workerThreads": false, + "timeout": "30s" + } +} diff --git a/src/build/napi-build.zig b/src/build/napi-build.zig index 50abee6..3ebf64c 100644 --- a/src/build/napi-build.zig +++ b/src/build/napi-build.zig @@ -4,7 +4,48 @@ fn getEnvVarOptional(build: *std.Build, name: []const u8) ?[]const u8 { return build.graph.environ_map.get(name); } -pub fn cloneLibraryOptions(build: *std.Build, option: NativeAddonBuildOptionsWithModule, target: std.Build.ResolvedTarget) std.Build.LibraryOptions { +fn pathExists(build: *std.Build, path: []const u8) bool { + std.Io.Dir.cwd().access(build.graph.io, path, .{}) catch return false; + return true; +} + +fn findLibnodeDllInPathList(build: *std.Build, paths: []const u8) ?[]const u8 { + var iter = std.mem.splitScalar(u8, paths, std.fs.path.delimiter); + while (iter.next()) |dir| { + if (dir.len == 0) continue; + if (pathExists(build, build.pathJoin(&.{ dir, "libnode.dll" }))) { + return dir; + } + } + return null; +} + +fn requireWindowsGnuLibnodePath(build: *std.Build) []const u8 { + if (getEnvVarOptional(build, "LIBNODE_PATH")) |libnode_path| { + if (pathExists(build, libnode_path)) { + if (pathExists(build, build.pathJoin(&.{ libnode_path, "libnode.dll" }))) { + return libnode_path; + } + std.debug.panic("libnode.dll not found in {s}", .{libnode_path}); + } + } + + if (getEnvVarOptional(build, "LIBPATH")) |paths| { + if (findLibnodeDllInPathList(build, paths)) |libnode_path| { + return libnode_path; + } + } + + if (getEnvVarOptional(build, "PATH")) |paths| { + if (findLibnodeDllInPathList(build, paths)) |libnode_path| { + return libnode_path; + } + } + + @panic("libnode.dll not found in any search path"); +} + +fn cloneLibraryOptionsInternal(build: *std.Build, option: anytype, target: std.Build.ResolvedTarget) std.Build.LibraryOptions { const root_module = build.createModule(.{ .root_source_file = option.root_module_options.root_source_file, .target = target, @@ -32,7 +73,7 @@ pub fn cloneLibraryOptions(build: *std.Build, option: NativeAddonBuildOptionsWit return std.Build.LibraryOptions{ .name = option.name, .root_module = root_module, - // Keep the linkage as dynami + // Keep the linkage as dynamic. .linkage = .dynamic, .version = option.version, .max_rss = option.max_rss, @@ -43,6 +84,10 @@ pub fn cloneLibraryOptions(build: *std.Build, option: NativeAddonBuildOptionsWit }; } +pub fn cloneLibraryOptions(build: *std.Build, option: NativeAddonBuildOptionsWithModule, target: std.Build.ResolvedTarget) std.Build.LibraryOptions { + return cloneLibraryOptionsInternal(build, option, target); +} + pub fn resolveNdkPath(build: *std.Build) ![]const u8 { if (getEnvVarOptional(build, "OHOS_NDK_HOME")) |home| { return build.pathJoin(&.{ home, "native" }); @@ -97,8 +142,87 @@ pub const NativeAddonBuildResult = struct { x64: ?*std.Build.Step.Compile, }; +pub const NodeAddonBuildResult = *std.Build.Step.Compile; + +pub const NapiVersion = enum(i32) { + v1 = 1, + v2 = 2, + v3 = 3, + v4 = 4, + v5 = 5, + v6 = 6, + v7 = 7, + v8 = 8, + v9 = 9, + v10 = 10, + experimental = std.math.maxInt(i32), +}; + +pub const NodeApiOptions = struct { + version: NapiVersion = .v8, + experimental: bool = false, + + fn effectiveVersion(self: NodeApiOptions) i32 { + return if (self.experimental) @intFromEnum(NapiVersion.experimental) else @intFromEnum(self.version); + } +}; + +fn nodePlatform(target: std.Target) []const u8 { + return switch (target.os.tag) { + .macos => "darwin", + .windows => "win32", + .linux => "linux", + .freebsd => "freebsd", + .ios => "ios", + else => @tagName(target.os.tag), + }; +} + +fn nodeArch(target: std.Target) []const u8 { + return switch (target.cpu.arch) { + .aarch64 => "arm64", + .x86_64 => "x64", + .x86 => "ia32", + .arm => "arm", + else => @tagName(target.cpu.arch), + }; +} + +fn nodeAbi(target: std.Target) ?[]const u8 { + return switch (target.os.tag) { + .windows => switch (target.abi) { + .msvc => "msvc", + .gnu => "gnu", + .none => null, + else => @tagName(target.abi), + }, + .linux => switch (target.abi) { + .gnu => "gnu", + .musl => "musl", + .none => null, + else => @tagName(target.abi), + }, + else => null, + }; +} + +pub fn nodePlatformArchAbi(build: *std.Build, target: std.Build.ResolvedTarget) []const u8 { + const platform = nodePlatform(target.result); + const arch = nodeArch(target.result); + if (nodeAbi(target.result)) |abi| { + return build.fmt("{s}-{s}-{s}", .{ platform, arch, abi }); + } + return build.fmt("{s}-{s}", .{ platform, arch }); +} + +pub fn nodeAddonFilename(build: *std.Build, name: []const u8, target: std.Build.ResolvedTarget) []const u8 { + return build.fmt("{s}.{s}.node", .{ name, nodePlatformArchAbi(build, target) }); +} + pub const NativeAddonBuildOptionsWithModule = struct { name: []const u8, + napi_module: ?*std.Build.Module = null, + node_api: NodeApiOptions = .{}, root_module_options: std.Build.Module.CreateOptions, version: ?std.SemanticVersion = null, max_rss: usize = 0, @@ -108,6 +232,41 @@ pub const NativeAddonBuildOptionsWithModule = struct { win32_manifest: ?std.Build.LazyPath = null, }; +fn isDefaultNodeApiOptions(options: NodeApiOptions) bool { + const default: NodeApiOptions = .{}; + return options.effectiveVersion() == default.effectiveVersion() and options.experimental == default.experimental; +} + +fn addConfiguredNapiImport( + build: *std.Build, + root_module: *std.Build.Module, + napi_module: ?*std.Build.Module, + build_options_module: *std.Build.Module, + comptime node_addon: bool, +) void { + root_module.addImport("build_options", build_options_module); + if (napi_module) |module| { + root_module.addImport("napi", createConfiguredNapiModule(build, module, build_options_module, node_addon)); + } +} + +pub const NodeAddonBuildOptionsWithModule = struct { + name: []const u8, + napi_module: *std.Build.Module, + root_module_options: std.Build.Module.CreateOptions, + node_api: NodeApiOptions = .{}, + /// Optional Windows import library override. + /// MSVC follows napi-rs and does not require this by default. GNU follows + /// napi-rs' `LIBNODE_PATH`/`LIBPATH`/`PATH` libnode.dll search. + node_import_lib: ?std.Build.LazyPath = null, + version: ?std.SemanticVersion = null, + max_rss: usize = 0, + use_llvm: ?bool = null, + use_lld: ?bool = null, + zig_lib_dir: ?std.Build.LazyPath = null, + win32_manifest: ?std.Build.LazyPath = null, +}; + var cached_arkvm_test_build: ?*std.Build = null; var cached_arkvm_test_value: bool = false; @@ -119,12 +278,48 @@ fn isArkvmTestBuild(build: *std.Build) bool { return cached_arkvm_test_value; } -fn createAddonBuildOptions(build: *std.Build) *std.Build.Step.Options { +const AddonBuildOptionsConfig = struct { + napi_tsgen: bool = false, + node_addon: bool = false, + node_api: NodeApiOptions = .{}, +}; + +fn createAddonBuildOptions(build: *std.Build, config: AddonBuildOptionsConfig) *std.Build.Step.Options { const options = build.addOptions(); - options.addOption(bool, "napi_tsgen", false); + options.addOption(bool, "napi_tsgen", config.napi_tsgen); + options.addOption(bool, "node_addon", config.node_addon); + options.addOption(i32, "napi_version", config.node_api.effectiveVersion()); + options.addOption(bool, "napi_experimental", config.node_api.experimental); return options; } +fn createConfiguredNapiModule( + build: *std.Build, + napi_module: *std.Build.Module, + build_options_module: *std.Build.Module, + comptime node_addon: bool, +) *std.Build.Module { + const package = napi_module.owner; + const header_path = package.path("src/sys/ohos"); + + const napi_sys = build.createModule(.{ + .root_source_file = package.path("src/sys/api.zig"), + }); + const napi = build.createModule(.{ + .root_source_file = package.path("src/napi.zig"), + }); + + napi_sys.addImport("build_options", build_options_module); + napi.addImport("napi-sys", napi_sys); + napi.addImport("build_options", build_options_module); + if (!node_addon) { + napi.addIncludePath(header_path); + napi_sys.addIncludePath(header_path); + } + + return napi; +} + fn arkvmHostAddonBuild(build: *std.Build, option: NativeAddonBuildOptionsWithModule) *std.Build.Step.Compile { const target = build.resolveTargetQuery(.{ .cpu_arch = .x86_64, @@ -138,7 +333,11 @@ fn arkvmHostAddonBuild(build: *std.Build, option: NativeAddonBuildOptionsWithMod const compile = build.addLibrary(hostOption); compile.linker_allow_shlib_undefined = true; compile.root_module.link_libc = true; - compile.root_module.addOptions("build_options", createAddonBuildOptions(build)); + const addon_build_options = createAddonBuildOptions(build, .{ + .node_api = option.node_api, + }); + const build_options_module = addon_build_options.createModule(); + addConfiguredNapiImport(build, compile.root_module, option.napi_module, build_options_module, false); const installStep = build.addInstallArtifact(compile, .{ .dest_dir = .{ @@ -152,10 +351,60 @@ fn arkvmHostAddonBuild(build: *std.Build, option: NativeAddonBuildOptionsWithMod return compile; } +pub fn nodeAddonBuild(build: *std.Build, option: NodeAddonBuildOptionsWithModule) !NodeAddonBuildResult { + const addon_build_options = createAddonBuildOptions(build, .{ + .node_addon = true, + .node_api = option.node_api, + }); + const target = option.root_module_options.target orelse build.graph.host; + + var nodeOption = cloneLibraryOptionsInternal(build, option, target); + nodeOption.linkage = .dynamic; + + const compile = build.addLibrary(nodeOption); + const build_options_module = addon_build_options.createModule(); + addConfiguredNapiImport(build, compile.root_module, option.napi_module, build_options_module, true); + compile.linker_allow_shlib_undefined = true; + if (target.result.os.tag == .windows) { + if (option.node_import_lib) |node_import_lib| { + compile.root_module.addObjectFile(node_import_lib); + } else if (getEnvVarOptional(build, "NODE_LIB_FILE")) |node_lib_file| { + compile.root_module.addObjectFile(.{ .cwd_relative = node_lib_file }); + } else if (getEnvVarOptional(build, "NODE_LIB_DIR")) |node_lib_dir| { + compile.root_module.addLibraryPath(.{ .cwd_relative = node_lib_dir }); + compile.root_module.linkSystemLibrary("node", .{ .use_pkg_config = .no }); + } else if (target.result.abi == .gnu) { + const libnode_path = requireWindowsGnuLibnodePath(build); + compile.root_module.addLibraryPath(.{ .cwd_relative = libnode_path }); + compile.root_module.linkSystemLibrary("node", .{ .use_pkg_config = .no }); + } + } + + const nodeDistDir = "node"; + const outputFilename = nodeAddonFilename(build, option.name, target); + const installStep = build.addInstallArtifact(compile, .{ + .dest_dir = .{ + .override = .{ + .custom = nodeDistDir, + }, + }, + .implib_dir = if (target.result.os.tag == .windows) .{ + .override = .{ + .custom = nodeDistDir, + }, + } else .disabled, + .dest_sub_path = outputFilename, + }); + build.getInstallStep().dependOn(&installStep.step); + + return compile; +} + pub const TypeDefinitionBuildOptions = struct { root_source_file: std.Build.LazyPath, output: std.Build.LazyPath, napi_module: *std.Build.Module, + node_api: NodeApiOptions = .{}, // Optional text injected after the generated banner comments. header: ?[]const u8 = null, options: ?*std.Build.Step.Options = null, @@ -164,8 +413,10 @@ pub const TypeDefinitionBuildOptions = struct { pub fn generateTypeDefinition(build: *std.Build, option: TypeDefinitionBuildOptions) !*std.Build.Step.Run { _ = isArkvmTestBuild(build); - const tsgen_build_options = build.addOptions(); - tsgen_build_options.addOption(bool, "napi_tsgen", true); + const tsgen_build_options = createAddonBuildOptions(build, .{ + .napi_tsgen = true, + .node_api = option.node_api, + }); const tsgen_napi_sys = build.addModule("zig-napi-tsgen-napi-sys", .{ .root_source_file = option.napi_module.owner.path("src/sys/api.zig"), @@ -173,10 +424,12 @@ pub fn generateTypeDefinition(build: *std.Build, option: TypeDefinitionBuildOpti const tsgen_napi = build.addModule("zig-napi-tsgen-napi", .{ .root_source_file = option.napi_module.owner.path("src/napi.zig"), }); + const tsgen_build_options_module = tsgen_build_options.createModule(); + tsgen_napi_sys.addImport("build_options", tsgen_build_options_module); tsgen_napi.addImport("napi-sys", tsgen_napi_sys); - tsgen_napi.addOptions("build_options", tsgen_build_options); - tsgen_napi.addIncludePath(option.napi_module.owner.path("src/sys/header")); - tsgen_napi_sys.addIncludePath(option.napi_module.owner.path("src/sys/header")); + tsgen_napi.addImport("build_options", tsgen_build_options_module); + tsgen_napi.addIncludePath(option.napi_module.owner.path("src/sys/ohos")); + tsgen_napi_sys.addIncludePath(option.napi_module.owner.path("src/sys/ohos")); const generator_root = build.createModule(.{ .root_source_file = option.napi_module.owner.path("src/build/napi-tsgen.zig"), @@ -198,7 +451,10 @@ pub fn generateTypeDefinition(build: *std.Build, option: TypeDefinitionBuildOpti }, }, }); - addon_root.addOptions("build_options", option.options orelse createAddonBuildOptions(build)); + const addon_build_options = option.options orelse createAddonBuildOptions(build, .{ + .node_api = option.node_api, + }); + addon_root.addImport("build_options", addon_build_options.createModule()); const ndk_root = try resolveNdkPath(build); if (ndk_root.len > 0) { @@ -226,13 +482,20 @@ pub fn generateTypeDefinition(build: *std.Build, option: TypeDefinitionBuildOpti } pub fn nativeAddonBuild(build: *std.Build, option: NativeAddonBuildOptionsWithModule) !NativeAddonBuildResult { + if (option.napi_module == null and !isDefaultNodeApiOptions(option.node_api)) { + std.debug.panic("nativeAddonBuild requires .napi_module when .node_api is configured so the napi wrapper sees the selected N-API version", .{}); + } + const arkvm_test = isArkvmTestBuild(build); if (arkvm_test) { const host = arkvmHostAddonBuild(build, option); return .{ .arm64 = null, .arm = null, .x64 = host }; } - const addon_build_options = createAddonBuildOptions(build); + const addon_build_options = createAddonBuildOptions(build, .{ + .node_api = option.node_api, + }); + const build_options_module = addon_build_options.createModule(); const currentTarget = if (option.root_module_options.target) |target| target.result else build.graph.host.result; @@ -257,7 +520,7 @@ pub fn nativeAddonBuild(build: *std.Build, option: NativeAddonBuildOptionsWithMo const arm64Option = cloneLibraryOptions(build, option, target); arm64 = build.addLibrary(arm64Option); - arm64.?.root_module.addOptions("build_options", addon_build_options); + addConfiguredNapiImport(build, arm64.?.root_module, option.napi_module, build_options_module, false); try linkNapi(build, arm64.?, target.query); const arm64DistDir: []const u8 = build.dupePath("arm64-v8a"); @@ -274,7 +537,7 @@ pub fn nativeAddonBuild(build: *std.Build, option: NativeAddonBuildOptionsWithMo const target = build.resolveTargetQuery(targets[1]); const armOption = cloneLibraryOptions(build, option, target); arm = build.addLibrary(armOption); - arm.?.root_module.addOptions("build_options", addon_build_options); + addConfiguredNapiImport(build, arm.?.root_module, option.napi_module, build_options_module, false); try linkNapi(build, arm.?, target.query); const armDistDir: []const u8 = build.dupePath("armeabi-v7a"); @@ -293,7 +556,7 @@ pub fn nativeAddonBuild(build: *std.Build, option: NativeAddonBuildOptionsWithMo // TODO: https://github.com/ziglang/zig/issues/25335 x64Option.use_llvm = true; x64 = build.addLibrary(x64Option); - x64.?.root_module.addOptions("build_options", addon_build_options); + addConfiguredNapiImport(build, x64.?.root_module, option.napi_module, build_options_module, false); try linkNapi(build, x64.?, target.query); const x64DistDir: []const u8 = build.dupePath("x86_64"); diff --git a/src/build/options.zig b/src/build/options.zig index dbf72ef..95c46ac 100644 --- a/src/build/options.zig +++ b/src/build/options.zig @@ -1 +1,4 @@ pub const napi_tsgen = false; +pub const node_addon = false; +pub const napi_version: i32 = 8; +pub const napi_experimental = false; diff --git a/src/napi.zig b/src/napi.zig index 2c1a189..b690a06 100644 --- a/src/napi.zig +++ b/src/napi.zig @@ -16,8 +16,12 @@ const typedarray = @import("./napi/wrapper/typedarray.zig"); const dataview = @import("./napi/wrapper/dataview.zig"); const reference = @import("./napi/wrapper/reference.zig"); const global_allocator = @import("./napi/util/allocator.zig"); +const options = @import("./napi/options.zig"); pub const napi_sys = @import("napi-sys"); +pub const NapiVersion = options.NapiVersion; +pub const selectedNapiVersion = options.selectedNapiVersion; +pub const experimentalEnabled = options.experimentalEnabled; pub const Env = env.Env; pub const Object = value.Object; pub const Number = value.Number; diff --git a/src/napi/async.zig b/src/napi/async.zig index 435067a..eb8fdbb 100644 --- a/src/napi/async.zig +++ b/src/napi/async.zig @@ -10,6 +10,7 @@ const GlobalAllocator = @import("./util/allocator.zig"); const AbortSignalModule = @import("./abort_signal.zig"); const AbortSignal = @import("./abort_signal.zig").AbortSignal; const AbortRegistration = @import("./abort_signal.zig").AbortRegistration; +const options = @import("./options.zig"); var threaded_runtime_mutex: std.atomic.Mutex = .unlocked; var threaded_runtime_initialized = false; @@ -287,14 +288,18 @@ fn validateTaskRunSignature(comptime Input: type, comptime Result: type, comptim } pub fn Async(comptime Result: type, comptime runtime: RuntimeModel) type { + comptime options.requireNapiVersion(.v4); return AsyncTaskDescriptor(Result, void, runtime); } pub fn AsyncWithEvents(comptime Result: type, comptime Event: type, comptime runtime: RuntimeModel) type { + comptime options.requireNapiVersion(.v4); return AsyncTaskDescriptor(Result, Event, runtime); } fn AsyncTaskDescriptor(comptime Result: type, comptime Event: type, comptime runtime: RuntimeModel) type { + comptime options.requireNapiVersion(.v4); + return struct { pub const is_napi_async_descriptor = true; pub const async_result_type = Result; diff --git a/src/napi/options.zig b/src/napi/options.zig new file mode 100644 index 0000000..37d7db8 --- /dev/null +++ b/src/napi/options.zig @@ -0,0 +1,55 @@ +const std = @import("std"); +const build_options = @import("build_options"); + +pub const NapiVersion = enum(i32) { + v1 = 1, + v2 = 2, + v3 = 3, + v4 = 4, + v5 = 5, + v6 = 6, + v7 = 7, + v8 = 8, + v9 = 9, + v10 = 10, + experimental = std.math.maxInt(i32), + + pub inline fn isAtLeast(self: NapiVersion, min_version: NapiVersion) bool { + return @intFromEnum(self) >= @intFromEnum(min_version); + } +}; + +pub fn selectedNapiVersion() NapiVersion { + return @enumFromInt(build_options.napi_version); +} + +pub fn experimentalEnabled() bool { + return build_options.napi_experimental; +} + +pub fn isNodeAddon() bool { + return build_options.node_addon; +} + +pub fn isOhosAddon() bool { + return !build_options.node_addon; +} + +pub fn requireNapiVersion(comptime required: NapiVersion) void { + const selected = comptime selectedNapiVersion(); + if (!selected.isAtLeast(required)) { + const required_name = comptime @tagName(required); + const selected_name = comptime @tagName(selected); + @compileError(std.fmt.comptimePrint( + \\[ Node-API Version Mismatch ] + \\Expected `{[required_name]s}` (N-API {[required_number]d}) or greater, got `{[selected_name]s}` (N-API {[selected_number]d}). + \\Set `.node_api.version = .{[required_name]s}` in `nodeAddonBuild` or `nativeAddonBuild`. + \\ + , .{ + .required_name = required_name, + .required_number = @intFromEnum(required), + .selected_name = selected_name, + .selected_number = @intFromEnum(selected), + })); + } +} diff --git a/src/napi/util/napi.zig b/src/napi/util/napi.zig index 1f9ae16..fd0351e 100644 --- a/src/napi/util/napi.zig +++ b/src/napi/util/napi.zig @@ -12,6 +12,7 @@ const ArrayBuffer = @import("../wrapper/arraybuffer.zig").ArrayBuffer; const DataView = @import("../wrapper/dataview.zig").DataView; const AbortSignal = @import("../abort_signal.zig").AbortSignal; const GlobalAllocator = @import("./allocator.zig"); +const options = @import("../options.zig"); fn napiTypeOf(env: napi.napi_env, raw: napi.napi_value) napi.napi_valuetype { var value_type: napi.napi_valuetype = undefined; @@ -132,6 +133,10 @@ fn valueMatchesType(env: napi.napi_env, raw: napi.napi_value, comptime T: type) switch (T) { NapiValue.Number => return napiTypeOf(env, raw) == napi.napi_number, NapiValue.String => return napiTypeOf(env, raw) == napi.napi_string, + NapiValue.BigInt => { + comptime options.requireNapiVersion(.v6); + return napiTypeOf(env, raw) == napi.napi_bigint; + }, NapiValue.Bool => return napiTypeOf(env, raw) == napi.napi_boolean, NapiValue.Object => return isPlainObjectValue(env, raw), NapiValue.Promise => return isPromiseValue(env, raw), @@ -224,35 +229,53 @@ pub const Napi = struct { switch (@typeInfo(T)) { .bool => { var result: bool = false; - _ = napi.napi_get_value_bool(env, raw, &result); + const status = napi.napi_get_value_bool(env, raw, &result); + if (status != napi.napi_ok) { + NapiError.last_error = NapiError.Error.withStatus(NapiError.Status.New(status)); + } return result; }, .float => { var result: f64 = 0; - _ = napi.napi_get_value_double(env, raw, &result); + const status = napi.napi_get_value_double(env, raw, &result); + if (status != napi.napi_ok) { + NapiError.last_error = NapiError.Error.withStatus(NapiError.Status.New(status)); + } return @floatCast(result); }, .int => |int| { if (int.signedness == .signed) { if (int.bits <= 32) { var result: i32 = 0; - _ = napi.napi_get_value_int32(env, raw, &result); + const status = napi.napi_get_value_int32(env, raw, &result); + if (status != napi.napi_ok) { + NapiError.last_error = NapiError.Error.withStatus(NapiError.Status.New(status)); + } return @intCast(result); } var result: i64 = 0; - _ = napi.napi_get_value_int64(env, raw, &result); + const status = napi.napi_get_value_int64(env, raw, &result); + if (status != napi.napi_ok) { + NapiError.last_error = NapiError.Error.withStatus(NapiError.Status.New(status)); + } return @intCast(result); } if (int.bits <= 32) { var result: u32 = 0; - _ = napi.napi_get_value_uint32(env, raw, &result); + const status = napi.napi_get_value_uint32(env, raw, &result); + if (status != napi.napi_ok) { + NapiError.last_error = NapiError.Error.withStatus(NapiError.Status.New(status)); + } return @intCast(result); } var result: i64 = 0; - _ = napi.napi_get_value_int64(env, raw, &result); + const status = napi.napi_get_value_int64(env, raw, &result); + if (status != napi.napi_ok) { + NapiError.last_error = NapiError.Error.withStatus(NapiError.Status.New(status)); + } return @intCast(result); }, else => @compileError("Unsupported fast from_napi_value type: " ++ @typeName(T)), diff --git a/src/napi/value/array.zig b/src/napi/value/array.zig index ba3d5ea..56b2c54 100644 --- a/src/napi/value/array.zig +++ b/src/napi/value/array.zig @@ -7,6 +7,8 @@ const ArrayList = std.ArrayList; const NapiError = @import("../wrapper/error.zig"); const GlobalAllocator = @import("../util/allocator.zig"); const typedarray = @import("../wrapper/typedarray.zig"); +const ArrayBuffer = @import("../wrapper/arraybuffer.zig").ArrayBuffer; +const options = @import("../options.zig"); pub const Array = struct { env: napi.napi_env, @@ -174,15 +176,17 @@ pub const Array = struct { const source: []const f64 = if (len == 0 or data == null) &[_]f64{} else @as([*]const f64, @ptrCast(@alignCast(data)))[0..len]; for (out, source) |*dst, src| dst.* = numericCast(Dst, src); }, - napi.napi_bigint64_array => { - const source: []const i64 = if (len == 0 or data == null) &[_]i64{} else @as([*]const i64, @ptrCast(@alignCast(data)))[0..len]; - for (out, source) |*dst, src| dst.* = numericCast(Dst, src); - }, - napi.napi_biguint64_array => { - const source: []const u64 = if (len == 0 or data == null) &[_]u64{} else @as([*]const u64, @ptrCast(@alignCast(data)))[0..len]; - for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + else => { + if (options.selectedNapiVersion().isAtLeast(.v6) and raw_type == napi.napi_bigint64_array) { + const source: []const i64 = if (len == 0 or data == null) &[_]i64{} else @as([*]const i64, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + } else if (options.selectedNapiVersion().isAtLeast(.v6) and raw_type == napi.napi_biguint64_array) { + const source: []const u64 = if (len == 0 or data == null) &[_]u64{} else @as([*]const u64, @ptrCast(@alignCast(data)))[0..len]; + for (out, source) |*dst, src| dst.* = numericCast(Dst, src); + } else { + unreachable; + } }, - else => unreachable, } } @@ -195,6 +199,9 @@ pub const Array = struct { _ = napi.napi_get_typedarray_info(env, raw, &raw_type, &len, &data, &arraybuffer, &byte_offset); + const arraybuffer_value = ArrayBuffer.from_raw(env, arraybuffer); + const element_len = typedarray.normalizeElementLength(len, raw_type, arraybuffer_value.length(), byte_offset); + const infos = @typeInfo(T); switch (infos) { @@ -204,7 +211,7 @@ pub const Array = struct { } var result: T = std.mem.zeroes(T); - const copy_len = @min(len, arr.len); + const copy_len = @min(element_len, arr.len); fillFromTypedArray(arr.child, result[0..copy_len], raw_type, data, copy_len); return result; }, @@ -217,8 +224,8 @@ pub const Array = struct { } const allocator = GlobalAllocator.globalAllocator(); - const buf = allocator.alloc(ptr.child, len) catch @panic("OOM"); - fillFromTypedArray(ptr.child, buf, raw_type, data, len); + const buf = allocator.alloc(ptr.child, element_len) catch @panic("OOM"); + fillFromTypedArray(ptr.child, buf, raw_type, data, element_len); return buf; }, .@"struct" => { @@ -230,10 +237,10 @@ pub const Array = struct { const allocator = GlobalAllocator.globalAllocator(); var result: T = ArrayList(child).empty; - result.ensureTotalCapacity(allocator, len) catch @panic("OOM"); - const items = allocator.alloc(child, len) catch @panic("OOM"); + result.ensureTotalCapacity(allocator, element_len) catch @panic("OOM"); + const items = allocator.alloc(child, element_len) catch @panic("OOM"); defer allocator.free(items); - fillFromTypedArray(child, items, raw_type, data, len); + fillFromTypedArray(child, items, raw_type, data, element_len); for (items) |item| { result.append(allocator, item) catch @panic("OOM"); } diff --git a/src/napi/value/bigint.zig b/src/napi/value/bigint.zig index 1bdcc3a..b7ea396 100644 --- a/src/napi/value/bigint.zig +++ b/src/napi/value/bigint.zig @@ -1,6 +1,7 @@ const napi = @import("napi-sys").napi_sys; const Env = @import("../env.zig").Env; const helper = @import("../util/helper.zig"); +const options = @import("../options.zig"); pub const BigInt = struct { env: napi.napi_env, @@ -8,10 +9,12 @@ pub const BigInt = struct { type: napi.napi_valuetype, pub fn from_raw(env: napi.napi_env, raw: napi.napi_value) BigInt { + comptime options.requireNapiVersion(.v6); return BigInt{ .env = env, .raw = raw, .type = napi.napi_bigint }; } pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { + comptime options.requireNapiVersion(.v6); const value_type = T; switch (value_type) { i64 => { @@ -33,6 +36,7 @@ pub const BigInt = struct { } pub fn New(env: Env, value: anytype) BigInt { + comptime options.requireNapiVersion(.v6); const value_type = @TypeOf(value); const infos = @typeInfo(value_type); diff --git a/src/napi/value/function.zig b/src/napi/value/function.zig index 96c64b4..80d4594 100644 --- a/src/napi/value/function.zig +++ b/src/napi/value/function.zig @@ -68,6 +68,13 @@ pub fn Function(comptime Args: type, comptime Return: type) type { if (cb_status != napi.napi_ok) { return NapiError.checkNapiStatus(inner_env, NapiError.Status.New(cb_status)); } + const copied_argc = @min(init_argc, expected_argc); + if (expected_argc > copied_argc) { + const undefined_value = Undefined.New(Env.from_raw(inner_env)); + for (copied_argc..expected_argc) |i| { + args_raw[i] = undefined_value.raw; + } + } var napi_params: std.meta.ArgsTuple(value_type) = undefined; var initialized_params: usize = 0; @@ -81,24 +88,20 @@ pub fn Function(comptime Args: type, comptime Return: type) type { var abort_signal: ?AbortSignal = null; inline for (params[env_index..], env_index..) |param_index, i| { - if (comptime @typeInfo(param_index.type.?) == .@"union") { - NapiError.clearLastError(); + NapiError.clearLastError(); + const converted = Napi.from_napi_value_auto(inner_env, args_raw[i - env_index], param_index.type.?); + if (NapiError.last_error) |last_err| { + last_err.throwInto(Env.from_raw(inner_env)); + const undefined_value = Undefined.New(Env.from_raw(inner_env)); + return undefined_value.raw; } - napi_params[i] = Napi.from_napi_value_auto(inner_env, args_raw[i - env_index], param_index.type.?); + napi_params[i] = converted; initialized_params = i + 1; if (comptime helper.isAbortSignal(param_index.type.?)) { abort_signal = napi_params[i]; } - if (comptime @typeInfo(param_index.type.?) == .@"union") { - if (NapiError.last_error) |last_err| { - last_err.throwInto(Env.from_raw(inner_env)); - const undefined_value = Undefined.New(Env.from_raw(inner_env)); - return undefined_value.raw; - } - } } - const copied_argc = @min(init_argc, expected_argc); const event_listener = if (has_async_events and copied_argc > params.len - env_index) args_raw[copied_argc - 1] else @@ -179,12 +182,15 @@ pub fn Function(comptime Args: type, comptime Return: type) type { /// Args should be a tuple. pub fn Call(self: Self, args: Args) !Return { const isTuple = ArgsInfos == .@"struct" and ArgsInfos.@"struct".is_tuple; + const isEmptyStruct = ArgsInfos == .@"struct" and ArgsInfos.@"struct".fields.len == 0; - const args_len = if (isTuple) ArgsInfos.@"struct".fields.len else 1; + const args_len = if (isEmptyStruct) 0 else if (isTuple) ArgsInfos.@"struct".fields.len else 1; var args_raw: [args_len]napi.napi_value = undefined; - if (isTuple) { + if (isEmptyStruct) { + // No arguments. + } else if (isTuple) { inline for (ArgsInfos.@"struct".fields, 0..) |arg, i| { args_raw[i] = try Napi.to_napi_value_auto(self.env, @field(args, arg.name), null); } @@ -196,7 +202,8 @@ pub fn Function(comptime Args: type, comptime Return: type) type { var result: napi.napi_value = undefined; - const status = napi.napi_call_function(self.env, this.raw, self.raw, args_len, args_raw[0..].ptr, &result); + const args_ptr = if (args_len == 0) null else args_raw[0..].ptr; + const status = napi.napi_call_function(self.env, this.raw, self.raw, args_len, args_ptr, &result); if (status != napi.napi_ok) { return NapiError.Error.fromStatus(NapiError.Status.New(status)); } diff --git a/src/napi/value/number.zig b/src/napi/value/number.zig index c4c3e80..3cd843d 100644 --- a/src/napi/value/number.zig +++ b/src/napi/value/number.zig @@ -90,7 +90,7 @@ pub const Number = struct { }, u64 => { var result: napi.napi_value = undefined; - _ = napi.napi_create_uint64(env.raw, @intCast(value), &result); + _ = napi.napi_create_double(env.raw, @floatFromInt(value), &result); return Number.from_raw(env.raw, result); }, else => { diff --git a/src/napi/wrapper/arraybuffer.zig b/src/napi/wrapper/arraybuffer.zig index 690c730..3a63f9c 100644 --- a/src/napi/wrapper/arraybuffer.zig +++ b/src/napi/wrapper/arraybuffer.zig @@ -104,13 +104,30 @@ pub const ArrayBuffer = struct { pub fn fromWithFinalizer(env: Env, data: []u8, on_finalize: ?*const fn () void) !ArrayBuffer { var result: napi.napi_value = undefined; + var result_data: ?*anyopaque = null; // Store the slice info for the finalizer const hint = ArrayBufferHint.create(data, on_finalize) catch { return NapiError.Error.fromStatus(NapiError.Status.GenericFailure); }; - const status = napi.napi_create_external_arraybuffer( + if (data.len == 0) { + const create_status = createArrayBuffer(env.raw, 0); + result = create_status.result; + result_data = create_status.data; + hint.destroy(); + if (create_status.raw != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(create_status.raw)); + } + return ArrayBuffer{ + .env = env.raw, + .raw = result, + .data = if (result_data == null) &[_]u8{} else @ptrCast(result_data), + .len = 0, + }; + } + + var status = napi.napi_create_external_arraybuffer( env.raw, @ptrCast(data.ptr), data.len, @@ -119,16 +136,36 @@ pub const ArrayBuffer = struct { &result, ); + var hint_destroyed = false; + if (isNoExternalBuffersAllowed(status)) { + const create_status = createArrayBuffer(env.raw, data.len); + result = create_status.result; + result_data = create_status.data; + status = create_status.raw; + if (status == napi.napi_ok) { + if (result_data == null) { + hint.destroy(); + return NapiError.Error.fromStatus(NapiError.Status.GenericFailure); + } + const dest: [*]u8 = @ptrCast(result_data); + @memcpy(dest[0..data.len], data); + } + hint.destroy(); + hint_destroyed = true; + } + if (status != napi.napi_ok) { // Clean up hint if buffer creation failed - hint.destroy(); + if (!hint_destroyed) { + hint.destroy(); + } return NapiError.Error.fromStatus(NapiError.Status.New(status)); } return ArrayBuffer{ .env = env.raw, .raw = result, - .data = data.ptr, + .data = if (result_data == null) data.ptr else @ptrCast(result_data), .len = data.len, }; } @@ -145,28 +182,26 @@ pub const ArrayBuffer = struct { /// const buf = try ArrayBuffer.copy(env, &stack_data); /// ``` pub fn copy(env: Env, data: []const u8) !ArrayBuffer { - var result: napi.napi_value = undefined; - var result_data: ?*anyopaque = null; - - const status = napi.napi_create_arraybuffer( - env.raw, - data.len, - &result_data, - &result, - ); + const create_status = createArrayBuffer(env.raw, data.len); + const status = create_status.raw; if (status != napi.napi_ok) { return NapiError.Error.fromStatus(NapiError.Status.New(status)); } // Copy the data into the newly created ArrayBuffer - const dest: [*]u8 = @ptrCast(result_data); - @memcpy(dest[0..data.len], data); + if (data.len > 0) { + if (create_status.data == null) { + return NapiError.Error.fromStatus(NapiError.Status.GenericFailure); + } + const dest: [*]u8 = @ptrCast(create_status.data); + @memcpy(dest[0..data.len], data); + } return ArrayBuffer{ .env = env.raw, - .raw = result, - .data = dest, + .raw = create_status.result, + .data = if (data.len == 0 or create_status.data == null) &[_]u8{} else @ptrCast(create_status.data), .len = data.len, }; } @@ -180,10 +215,8 @@ pub const ArrayBuffer = struct { /// @memset(buf.asSlice(), 0); // initialize /// ``` pub fn New(env: Env, len: usize) !ArrayBuffer { - var result: napi.napi_value = undefined; - var data: ?*anyopaque = null; - - const status = napi.napi_create_arraybuffer(env.raw, len, &data, &result); + const create_status = createArrayBuffer(env.raw, len); + const status = create_status.raw; if (status != napi.napi_ok) { return NapiError.Error.fromStatus(NapiError.Status.New(status)); @@ -191,8 +224,8 @@ pub const ArrayBuffer = struct { return ArrayBuffer{ .env = env.raw, - .raw = result, - .data = @ptrCast(data), + .raw = create_status.result, + .data = if (len == 0 or create_status.data == null) &[_]u8{} else @ptrCast(create_status.data), .len = len, }; } @@ -213,6 +246,27 @@ pub const ArrayBuffer = struct { } }; +const ArrayBufferCreateStatus = struct { + raw: napi.napi_status, + result: napi.napi_value, + data: ?*anyopaque, +}; + +fn createArrayBuffer(env: napi.napi_env, len: usize) ArrayBufferCreateStatus { + var result: napi.napi_value = undefined; + var data: ?*anyopaque = null; + const status = napi.napi_create_arraybuffer(env, len, &data, &result); + return .{ + .raw = status, + .result = result, + .data = data, + }; +} + +fn isNoExternalBuffersAllowed(status: napi.napi_status) bool { + return NapiError.Status.New(status) == .NoExternalBuffersAllowed; +} + /// Helper struct to store ArrayBuffer info for the finalizer const ArrayBufferHint = struct { allocator: std.mem.Allocator, diff --git a/src/napi/wrapper/buffer.zig b/src/napi/wrapper/buffer.zig index 570b49b..169df05 100644 --- a/src/napi/wrapper/buffer.zig +++ b/src/napi/wrapper/buffer.zig @@ -3,6 +3,7 @@ const napi = @import("napi-sys").napi_sys; const Env = @import("../env.zig").Env; const NapiError = @import("error.zig"); const GlobalAllocator = @import("../util/allocator.zig"); +const options = @import("../options.zig"); pub const Buffer = struct { env: napi.napi_env, @@ -110,7 +111,22 @@ pub const Buffer = struct { return NapiError.Error.fromStatus(NapiError.Status.GenericFailure); }; - const status = napi.napi_create_external_buffer( + if (data.len == 0) { + const status = createEmptyBuffer(env.raw); + result = status.result; + hint.destroy(); + if (status.raw != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status.raw)); + } + return Buffer{ + .env = env.raw, + .raw = result, + .data = if (status.data == null) &[_]u8{} else @ptrCast(status.data), + .len = 0, + }; + } + + var status = napi.napi_create_external_buffer( env.raw, data.len, @ptrCast(data.ptr), @@ -119,16 +135,34 @@ pub const Buffer = struct { &result, ); + var result_data: ?*anyopaque = null; + var hint_destroyed = false; + var used_copy_fallback = false; + if (isNoExternalBuffersAllowed(status)) { + const copy_status = createBufferCopy(env.raw, data); + result = copy_status.result; + result_data = copy_status.data; + status = copy_status.raw; + hint.destroy(); + hint_destroyed = true; + used_copy_fallback = true; + } + if (status != napi.napi_ok) { // Clean up hint if buffer creation failed - hint.destroy(); + if (!hint_destroyed) { + hint.destroy(); + } return NapiError.Error.fromStatus(NapiError.Status.New(status)); } + if (used_copy_fallback and result_data == null) { + return NapiError.Error.fromStatus(NapiError.Status.GenericFailure); + } return Buffer{ .env = env.raw, .raw = result, - .data = data.ptr, + .data = if (result_data == null) data.ptr else @ptrCast(result_data), .len = data.len, }; } @@ -145,25 +179,20 @@ pub const Buffer = struct { /// const buf = try Buffer.copy(env, &stack_data); /// ``` pub fn copy(env: Env, data: []const u8) !Buffer { - var result: napi.napi_value = undefined; - var result_data: ?*anyopaque = null; - - const status = napi.napi_create_buffer_copy( - env.raw, - data.len, - @ptrCast(data.ptr), - &result_data, - &result, - ); + const copy_status = if (data.len == 0) createEmptyBuffer(env.raw) else createBufferCopy(env.raw, data); + const status = copy_status.raw; if (status != napi.napi_ok) { return NapiError.Error.fromStatus(NapiError.Status.New(status)); } + if (data.len > 0 and copy_status.data == null) { + return NapiError.Error.fromStatus(NapiError.Status.GenericFailure); + } return Buffer{ .env = env.raw, - .raw = result, - .data = @ptrCast(result_data), + .raw = copy_status.result, + .data = if (data.len == 0 or copy_status.data == null) &[_]u8{} else @ptrCast(copy_status.data), .len = data.len, }; } @@ -180,7 +209,15 @@ pub const Buffer = struct { var result: napi.napi_value = undefined; var data: ?*anyopaque = null; - const status = napi.napi_create_buffer(env.raw, len, &data, &result); + const status = if (comptime options.isOhosAddon()) status: { + if (len == 0) { + const empty_status = createEmptyBuffer(env.raw); + result = empty_status.result; + data = empty_status.data; + break :status empty_status.raw; + } + break :status napi.napi_create_buffer(env.raw, len, &data, &result); + } else napi.napi_create_buffer(env.raw, len, &data, &result); if (status != napi.napi_ok) { return NapiError.Error.fromStatus(NapiError.Status.New(status)); @@ -189,7 +226,7 @@ pub const Buffer = struct { return Buffer{ .env = env.raw, .raw = result, - .data = @ptrCast(data), + .data = if (len == 0 or data == null) &[_]u8{} else @ptrCast(data), .len = len, }; } @@ -210,6 +247,52 @@ pub const Buffer = struct { } }; +const BufferCopyStatus = struct { + raw: napi.napi_status, + result: napi.napi_value, + data: ?*anyopaque, +}; + +fn createEmptyBuffer(env: napi.napi_env) BufferCopyStatus { + if (comptime options.isOhosAddon()) { + var result: napi.napi_value = undefined; + var data: ?*anyopaque = null; + const status = napi.napi_create_arraybuffer(env, 0, &data, &result); + return .{ + .raw = status, + .result = result, + .data = data, + }; + } + + return createBufferCopy(env, &[_]u8{}); +} + +fn createBufferCopy(env: napi.napi_env, data: []const u8) BufferCopyStatus { + var result: napi.napi_value = undefined; + var result_data: ?*anyopaque = null; + var empty: u8 = 0; + const source: ?*const anyopaque = if (data.len == 0) @ptrCast(&empty) else @ptrCast(data.ptr); + + const status = napi.napi_create_buffer_copy( + env, + data.len, + source, + &result_data, + &result, + ); + + return .{ + .raw = status, + .result = result, + .data = result_data, + }; +} + +fn isNoExternalBuffersAllowed(status: napi.napi_status) bool { + return NapiError.Status.New(status) == .NoExternalBuffersAllowed; +} + /// Helper struct to store buffer info for the finalizer const BufferHint = struct { allocator: std.mem.Allocator, diff --git a/src/napi/wrapper/class.zig b/src/napi/wrapper/class.zig index e6bb036..5d61cbb 100644 --- a/src/napi/wrapper/class.zig +++ b/src/napi/wrapper/class.zig @@ -68,6 +68,14 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { const argv = if (Argc == 0) null else args_raw[0..].ptr; const status = napi.napi_get_cb_info(env, callback_info, &argc, argv, this_obj, null); if (status != napi.napi_ok) return null; + if (Argc > argc) { + var undefined_raw: napi.napi_value = undefined; + const undefined_status = napi.napi_get_undefined(env, &undefined_raw); + if (undefined_status != napi.napi_ok) return null; + for (argc..Argc) |i| { + args_raw[i] = undefined_raw; + } + } return argc; } @@ -92,17 +100,14 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { var tuple_args: std.meta.ArgsTuple(init_fn_type) = undefined; inline for (init_fn_info.@"fn".params, 0..) |arg, i| { - if (comptime @typeInfo(arg.type.?) == .@"union") { - NapiError.clearLastError(); - } - tuple_args[i] = Napi.from_napi_value_auto(env, args_raw[i], arg.type.?); - if (comptime @typeInfo(arg.type.?) == .@"union") { - if (NapiError.last_error) |last_err| { - last_err.throwInto(napi_env.Env.from_raw(env)); - instance.destroyUninitialized(); - return null; - } + NapiError.clearLastError(); + const converted = Napi.from_napi_value_auto(env, args_raw[i], arg.type.?); + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(env)); + instance.destroyUninitialized(); + return null; } + tuple_args[i] = converted; } if (@typeInfo(init_fn_info.@"fn".return_type.?) == .error_union) { @@ -120,17 +125,14 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { data.* = std.mem.zeroes(T); if (comptime HasInit) { inline for (fields, 0..) |field, i| { - if (comptime @typeInfo(field.type) == .@"union") { - NapiError.clearLastError(); - } - @field(data.*, field.name) = Napi.from_napi_value_auto(env, args_raw[i], field.type); - if (comptime @typeInfo(field.type) == .@"union") { - if (NapiError.last_error) |last_err| { - last_err.throwInto(napi_env.Env.from_raw(env)); - instance.destroyUninitialized(); - return null; - } + NapiError.clearLastError(); + const converted = Napi.from_napi_value_auto(env, args_raw[i], field.type); + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(env)); + instance.destroyUninitialized(); + return null; } + @field(data.*, field.name) = converted; } } } @@ -163,16 +165,13 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { } else { var tuple_args: std.meta.ArgsTuple(factory_fn_type) = undefined; inline for (params, 0..) |param, i| { - if (comptime @typeInfo(param.type.?) == .@"union") { - NapiError.clearLastError(); - } - tuple_args[i] = Napi.from_napi_value_auto(env, args_raw[i], param.type.?); - if (comptime @typeInfo(param.type.?) == .@"union") { - if (NapiError.last_error) |last_err| { - last_err.throwInto(napi_env.Env.from_raw(env)); - return null; - } + NapiError.clearLastError(); + const converted = Napi.from_napi_value_auto(env, args_raw[i], param.type.?); + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(env)); + return null; } + tuple_args[i] = converted; } if (@typeInfo(factory_fn_info.@"fn".return_type.?) == .error_union) { instance_data = @call(.auto, factory_fn, tuple_args) catch return null; @@ -290,15 +289,11 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { const instance: *InstanceData = @ptrCast(@alignCast(data.?)); if (actual_argc > 0) { - if (comptime @typeInfo(field.type) == .@"union") { - NapiError.clearLastError(); - } + NapiError.clearLastError(); const new_value = Napi.from_napi_value_auto(setter_env, args_raw[0], field.type); - if (comptime @typeInfo(field.type) == .@"union") { - if (NapiError.last_error) |last_err| { - last_err.throwInto(napi_env.Env.from_raw(setter_env)); - return null; - } + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(setter_env)); + return null; } @field(instance.value, field.name) = new_value; } @@ -429,17 +424,14 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { // inject args inline for (method_info.@"fn".params[method_args_offset..], method_args_offset..) |param, i| { - if (comptime @typeInfo(param.type.?) == .@"union") { - NapiError.clearLastError(); + NapiError.clearLastError(); + const converted = Napi.from_napi_value_auto(method_env, args_raw[i - method_args_offset], param.type.?); + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(method_env)); + return null; } - tuple_args[i] = Napi.from_napi_value_auto(method_env, args_raw[i - method_args_offset], param.type.?); + tuple_args[i] = converted; initialized_args = i + 1; - if (comptime @typeInfo(param.type.?) == .@"union") { - if (NapiError.last_error) |last_err| { - last_err.throwInto(napi_env.Env.from_raw(method_env)); - return null; - } - } } const result = @call(.auto, method, tuple_args); return Napi.to_napi_value_auto(method_env, result, fn_name) catch null; diff --git a/src/napi/wrapper/status.zig b/src/napi/wrapper/status.zig index 8c3a4d6..675c0aa 100644 --- a/src/napi/wrapper/status.zig +++ b/src/napi/wrapper/status.zig @@ -1,4 +1,5 @@ const napi = @import("napi-sys").napi_sys; +const build_options = @import("build_options"); // Copy from napi-rs pub const Status = enum(u32) { @@ -27,14 +28,16 @@ pub const Status = enum(u32) { DetachableArraybufferExpected, WouldDeadlock, NoExternalBuffersAllowed, + CannotRunJs, + RuntimeSpecific24, Unknown = 1024, // unknown status. for example, using napi3 module in napi7 Node.js, and generate an invalid napi3 status pub fn from_raw(raw: napi.napi_status) Status { - return @as(Status, @enumFromInt(@as(u32, raw))); + return @as(Status, @enumFromInt(@as(u32, @intCast(raw)))); } pub fn New(status: anytype) Status { - return @as(Status, @enumFromInt(@as(u32, status))); + return @as(Status, @enumFromInt(@as(u32, @intCast(status)))); } pub fn ToString(self: Status) []const u8 { @@ -61,7 +64,9 @@ pub const Status = enum(u32) { .ArrayBufferExpected => "ArrayBufferExpected", .DetachableArraybufferExpected => "DetachableArraybufferExpected", .WouldDeadlock => "WouldDeadlock", - .NoExternalBuffersAllowed => "NoExternalBuffersAllowed", + .NoExternalBuffersAllowed => if (build_options.node_addon) "NoExternalBuffersAllowed" else "CreateArkRuntimeTooManyEnvs", + .CannotRunJs => if (build_options.node_addon) "CannotRunJs" else "CreateArkRuntimeOnlyOneEnvPerThread", + .RuntimeSpecific24 => if (build_options.node_addon) "RuntimeSpecific24" else "DestroyArkRuntimeEnvNotExist", else => "Unknown", }; } diff --git a/src/napi/wrapper/thread_safe_function.zig b/src/napi/wrapper/thread_safe_function.zig index 68cd47b..4fae599 100644 --- a/src/napi/wrapper/thread_safe_function.zig +++ b/src/napi/wrapper/thread_safe_function.zig @@ -7,6 +7,17 @@ const Env = @import("../env.zig").Env; const NapiError = @import("./error.zig"); const String = @import("../value/string.zig").String; const GlobalAllocator = @import("../util/allocator.zig"); +const options = @import("../options.zig"); + +const ThreadSafeFunctionCallModeRaw = if (options.selectedNapiVersion().isAtLeast(.v4)) + napi.napi_threadsafe_function_call_mode +else + c_int; + +const ThreadSafeFunctionReleaseModeRaw = if (options.selectedNapiVersion().isAtLeast(.v4)) + napi.napi_threadsafe_function_release_mode +else + c_int; pub const ThreadSafeFunctionMode = enum { NonBlocking, @@ -14,7 +25,8 @@ pub const ThreadSafeFunctionMode = enum { const Self = @This(); - pub fn to_raw(self: Self) napi.napi_threadsafe_function_call_mode { + pub fn to_raw(self: Self) ThreadSafeFunctionCallModeRaw { + comptime options.requireNapiVersion(.v4); return switch (self) { .NonBlocking => napi.napi_tsfn_nonblocking, .Blocking => napi.napi_tsfn_blocking, @@ -28,7 +40,8 @@ pub const ThreadSafeFunctionReleaseMode = enum { const Self = @This(); - pub fn to_raw(self: Self) napi.napi_threadsafe_function_release_mode { + pub fn to_raw(self: Self) ThreadSafeFunctionReleaseModeRaw { + comptime options.requireNapiVersion(.v4); return switch (self) { .Release => napi.napi_tsfn_release, .Abort => napi.napi_tsfn_abort, @@ -49,6 +62,8 @@ fn CallData(comptime Args: type) type { } pub fn ThreadSafeFunction(comptime Args: type, comptime Return: type, comptime ThreadSafeFunctionCalleeHandled: anytype, comptime MaxQueueSize: anytype) type { + comptime options.requireNapiVersion(.v4); + return struct { env: napi.napi_env, raw: napi.napi_value, diff --git a/src/napi/wrapper/typedarray.zig b/src/napi/wrapper/typedarray.zig index 50374e9..0d46d30 100644 --- a/src/napi/wrapper/typedarray.zig +++ b/src/napi/wrapper/typedarray.zig @@ -3,10 +3,12 @@ const napi = @import("napi-sys").napi_sys; const Env = @import("../env.zig").Env; const ArrayBuffer = @import("./arraybuffer.zig").ArrayBuffer; const NapiError = @import("./error.zig"); +const options = @import("../options.zig"); pub fn isSupportedElementType(comptime T: type) bool { return switch (T) { - i8, u8, i16, u16, i32, u32, f32, f64, i64, u64 => true, + i8, u8, i16, u16, i32, u32, f32, f64 => true, + i64, u64 => options.selectedNapiVersion().isAtLeast(.v6), else => false, }; } @@ -21,12 +23,44 @@ pub fn defaultTypeFor(comptime T: type) napi.napi_typedarray_type { u32 => napi.napi_uint32_array, f32 => napi.napi_float32_array, f64 => napi.napi_float64_array, - i64 => napi.napi_bigint64_array, - u64 => napi.napi_biguint64_array, + i64 => blk: { + comptime options.requireNapiVersion(.v6); + break :blk napi.napi_bigint64_array; + }, + u64 => blk: { + comptime options.requireNapiVersion(.v6); + break :blk napi.napi_biguint64_array; + }, else => @compileError("Unsupported TypedArray element type: " ++ @typeName(T)), }; } +pub fn elementByteSize(raw_type: napi.napi_typedarray_type) usize { + return switch (raw_type) { + napi.napi_int8_array, napi.napi_uint8_array, napi.napi_uint8_clamped_array => 1, + napi.napi_int16_array, napi.napi_uint16_array => 2, + napi.napi_int32_array, napi.napi_uint32_array, napi.napi_float32_array => 4, + napi.napi_float64_array => 8, + else => if (options.selectedNapiVersion().isAtLeast(.v6) and (raw_type == napi.napi_bigint64_array or raw_type == napi.napi_biguint64_array)) 8 else 0, + }; +} + +pub fn normalizeElementLength(raw_len: usize, raw_type: napi.napi_typedarray_type, arraybuffer_byte_length: usize, byte_offset: usize) usize { + const remaining_byte_len = arraybuffer_byte_length -| byte_offset; + const element_size = elementByteSize(raw_type); + if (element_size == 0) return 0; + + if (raw_len * element_size <= remaining_byte_len) { + return raw_len; + } + + if (raw_len <= remaining_byte_len and raw_len % element_size == 0) { + return raw_len / element_size; + } + + return 0; +} + fn validateElementType(comptime T: type) void { if (!comptime isSupportedElementType(T)) { @compileError("Unsupported TypedArray element type: " ++ @typeName(T)); @@ -68,13 +102,7 @@ pub fn TypedArray(comptime T: type) type { ); const arraybuffer = ArrayBuffer.from_raw(env, arraybuffer_raw); - const remaining_byte_len = arraybuffer.length() -| byte_offset; - const element_len = if (len * @sizeOf(T) <= remaining_byte_len) - len - else if (len <= remaining_byte_len and len % @sizeOf(T) == 0) - len / @sizeOf(T) - else - 0; + const element_len = normalizeElementLength(len, typedarray_type, arraybuffer.length(), byte_offset); return Self{ .env = env, @@ -164,5 +192,20 @@ pub const Int32Array = TypedArray(i32); pub const Uint32Array = TypedArray(u32); pub const Float32Array = TypedArray(f32); pub const Float64Array = TypedArray(f64); -pub const BigInt64Array = TypedArray(i64); -pub const BigUint64Array = TypedArray(u64); +pub const BigInt64Array = if (options.selectedNapiVersion().isAtLeast(.v6)) TypedArray(i64) else UnavailableTypedArray(i64, .v6); +pub const BigUint64Array = if (options.selectedNapiVersion().isAtLeast(.v6)) TypedArray(u64) else UnavailableTypedArray(u64, .v6); + +fn UnavailableTypedArray(comptime T: type, comptime required: options.NapiVersion) type { + return struct { + pub const is_napi_typedarray = true; + pub const element_type = T; + + fn unavailable() void { + options.requireNapiVersion(required); + } + + pub fn from_raw(_: napi.napi_env, _: napi.napi_value) @This() { + comptime unavailable(); + } + }; +} diff --git a/src/prelude/module.zig b/src/prelude/module.zig index a7c6867..6d5770a 100644 --- a/src/prelude/module.zig +++ b/src/prelude/module.zig @@ -7,6 +7,7 @@ const Object = @import("../napi/value.zig").Object; const NapiError = @import("../napi/wrapper/error.zig"); const Napi = @import("../napi/util/napi.zig").Napi; const Undefined = @import("../napi/value/undefined.zig").Undefined; +const options = @import("../napi/options.zig"); pub fn NODE_API_MODULE_WITH_INIT( comptime name: []const u8, @@ -102,12 +103,29 @@ pub fn NODE_API_MODULE_WITH_INIT( fn module_init() callconv(.c) void { napi.napi_module_register(@constCast(&module)); } + + fn node_init(env: napi.napi_env, exports: napi.napi_value) callconv(.c) napi.napi_value { + if (@hasDecl(napi, "setup")) { + napi.setup(); + } + return InitFn.inner_init(env, exports); + } + + fn node_api_version() callconv(.c) i32 { + return @intFromEnum(options.selectedNapiVersion()); + } }; comptime { - if (builtin.object_format == .elf) { - const init_array = [1]*const fn () callconv(.c) void{&ModuleImpl.module_init}; - @export(&init_array, .{ .linkage = .strong, .name = "init_array", .section = ".init_array" }); + if (build_options.node_addon) { + @export(&ModuleImpl.node_init, .{ .linkage = .strong, .name = "napi_register_module_v1" }); + @export(&ModuleImpl.node_api_version, .{ .linkage = .strong, .name = "node_api_module_get_api_version_v1" }); + } else if (builtin.object_format == .elf) { + const InitFnPtr = *const fn () callconv(.c) void; + const ElfInit = struct { + export const init_array: [1]InitFnPtr linksection(".init_array") = .{&ModuleImpl.module_init}; + }; + _ = ElfInit; } } } diff --git a/src/sys/api.zig b/src/sys/api.zig index b633c99..558296c 100644 --- a/src/sys/api.zig +++ b/src/sys/api.zig @@ -1,5 +1,13 @@ -const sys = @cImport({ - @cInclude("native_api.h"); -}); +const std = @import("std"); +const build_options = @import("build_options"); -pub const napi_sys = sys; +pub const napi_sys = if (build_options.node_addon) + @import("node.zig") +else + @cImport({ + @cDefine("NAPI_VERSION", std.fmt.comptimePrint("{d}", .{build_options.napi_version})); + if (build_options.napi_experimental) { + @cDefine("NAPI_EXPERIMENTAL", "1"); + } + @cInclude("native_api.h"); + }); diff --git a/src/sys/node.zig b/src/sys/node.zig new file mode 100644 index 0000000..f2cfe5d --- /dev/null +++ b/src/sys/node.zig @@ -0,0 +1,1033 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub const napi_env__ = opaque {}; +pub const napi_value__ = opaque {}; +pub const napi_ref__ = opaque {}; +pub const napi_handle_scope__ = opaque {}; +pub const napi_escapable_handle_scope__ = opaque {}; +pub const napi_callback_info__ = opaque {}; +pub const napi_deferred__ = opaque {}; +pub const napi_callback_scope__ = opaque {}; +pub const napi_async_context__ = opaque {}; +pub const napi_async_work__ = opaque {}; + +pub const napi_env = ?*napi_env__; +pub const node_api_basic_env = napi_env; +pub const napi_value = ?*napi_value__; +pub const napi_ref = ?*napi_ref__; +pub const napi_handle_scope = ?*napi_handle_scope__; +pub const napi_escapable_handle_scope = ?*napi_escapable_handle_scope__; +pub const napi_callback_info = ?*napi_callback_info__; +pub const napi_deferred = ?*napi_deferred__; +pub const napi_callback_scope = ?*napi_callback_scope__; +pub const napi_async_context = ?*napi_async_context__; +pub const napi_async_work = ?*napi_async_work__; + +pub const napi_property_attributes = c_int; +pub const napi_default: napi_property_attributes = 0; +pub const napi_writable: napi_property_attributes = 1 << 0; +pub const napi_enumerable: napi_property_attributes = 1 << 1; +pub const napi_configurable: napi_property_attributes = 1 << 2; +pub const napi_static: napi_property_attributes = 1 << 10; +pub const napi_default_method: napi_property_attributes = napi_writable | napi_configurable; +pub const napi_default_jsproperty: napi_property_attributes = napi_writable | napi_enumerable | napi_configurable; + +pub const napi_valuetype = c_int; +pub const napi_undefined: napi_valuetype = 0; +pub const napi_null: napi_valuetype = 1; +pub const napi_boolean: napi_valuetype = 2; +pub const napi_number: napi_valuetype = 3; +pub const napi_string: napi_valuetype = 4; +pub const napi_symbol: napi_valuetype = 5; +pub const napi_object: napi_valuetype = 6; +pub const napi_function: napi_valuetype = 7; +pub const napi_external: napi_valuetype = 8; + +pub const napi_typedarray_type = c_int; +pub const napi_int8_array: napi_typedarray_type = 0; +pub const napi_uint8_array: napi_typedarray_type = 1; +pub const napi_uint8_clamped_array: napi_typedarray_type = 2; +pub const napi_int16_array: napi_typedarray_type = 3; +pub const napi_uint16_array: napi_typedarray_type = 4; +pub const napi_int32_array: napi_typedarray_type = 5; +pub const napi_uint32_array: napi_typedarray_type = 6; +pub const napi_float32_array: napi_typedarray_type = 7; +pub const napi_float64_array: napi_typedarray_type = 8; + +pub const napi_status = c_int; +pub const napi_ok: napi_status = 0; +pub const napi_invalid_arg: napi_status = 1; +pub const napi_object_expected: napi_status = 2; +pub const napi_string_expected: napi_status = 3; +pub const napi_name_expected: napi_status = 4; +pub const napi_function_expected: napi_status = 5; +pub const napi_number_expected: napi_status = 6; +pub const napi_boolean_expected: napi_status = 7; +pub const napi_array_expected: napi_status = 8; +pub const napi_generic_failure: napi_status = 9; +pub const napi_pending_exception: napi_status = 10; +pub const napi_cancelled: napi_status = 11; +pub const napi_escape_called_twice: napi_status = 12; +pub const napi_handle_scope_mismatch: napi_status = 13; +pub const napi_callback_scope_mismatch: napi_status = 14; +pub const napi_queue_full: napi_status = 15; +pub const napi_closing: napi_status = 16; +pub const napi_bigint_expected: napi_status = 17; +pub const napi_date_expected: napi_status = 18; +pub const napi_arraybuffer_expected: napi_status = 19; +pub const napi_detachable_arraybuffer_expected: napi_status = 20; +pub const napi_would_deadlock: napi_status = 21; +pub const napi_no_external_buffers_allowed: napi_status = 22; +pub const napi_cannot_run_js: napi_status = 23; + +pub const napi_callback = ?*const fn (env: napi_env, info: napi_callback_info) callconv(.c) napi_value; +pub const napi_finalize = ?*const fn (env: napi_env, finalize_data: ?*anyopaque, finalize_hint: ?*anyopaque) callconv(.c) void; +pub const node_api_basic_finalize = napi_finalize; +pub const napi_cleanup_hook = ?*const fn (arg: ?*anyopaque) callconv(.c) void; +pub const napi_async_execute_callback = ?*const fn (env: napi_env, data: ?*anyopaque) callconv(.c) void; +pub const napi_async_complete_callback = ?*const fn (env: napi_env, status: napi_status, data: ?*anyopaque) callconv(.c) void; +pub const napi_addon_register_func = ?*const fn (env: napi_env, exports: napi_value) callconv(.c) napi_value; + +pub const napi_property_descriptor = extern struct { + utf8name: [*c]const u8, + name: napi_value, + method: napi_callback, + getter: napi_callback, + setter: napi_callback, + value: napi_value, + attributes: napi_property_attributes, + data: ?*anyopaque, +}; + +pub const napi_extended_error_info = extern struct { + error_message: [*c]const u8, + engine_reserved: ?*anyopaque, + engine_error_code: u32, + error_code: napi_status, +}; + +pub const napi_node_version = extern struct { + major: u32, + minor: u32, + patch: u32, + release: [*c]const u8, +}; + +pub const napi_module = extern struct { + nm_version: c_int, + nm_flags: c_uint, + nm_filename: [*c]const u8, + nm_register_func: napi_addon_register_func, + nm_modname: [*c]const u8, + nm_priv: ?*anyopaque, + reserved: [4]?*anyopaque, +}; + +const use_windows_msvc_dynamic_symbols = builtin.os.tag == .windows and builtin.abi == .msvc; + +const WindowsMsvcLoader = struct { + const windows = std.os.windows; + + extern "kernel32" fn GetModuleHandleExW(dwFlags: u32, lpModuleName: ?windows.LPCWSTR, phModule: *windows.HMODULE) callconv(.winapi) windows.BOOL; + extern "kernel32" fn GetProcAddress(hModule: windows.HMODULE, lpProcName: windows.LPCSTR) callconv(.winapi) ?windows.FARPROC; + + var initialized = false; + var host: ?windows.HMODULE = null; + + fn setup() void { + if (initialized) return; + var handle: windows.HMODULE = undefined; + if (GetModuleHandleExW(0, null, &handle).toBool()) { + host = handle; + } + initialized = true; + } + + fn lookup(comptime Fn: type, comptime name: [:0]const u8) ?Fn { + @This().setup(); + const module = host orelse return null; + const proc = GetProcAddress(module, name.ptr) orelse return null; + return @as(Fn, @ptrCast(@alignCast(proc))); + } + + fn SymbolCache(comptime Fn: type, comptime name: [:0]const u8) type { + return struct { + const symbol_name = name; + var initialized = false; + var symbol: ?Fn = null; + }; + } + + fn lookupCached(comptime Fn: type, comptime name: [:0]const u8) ?Fn { + const Cache = SymbolCache(Fn, name); + if (!Cache.initialized) { + Cache.symbol = lookup(Fn, name); + Cache.initialized = true; + } + return Cache.symbol; + } +}; + +pub fn setup() void { + if (use_windows_msvc_dynamic_symbols) { + WindowsMsvcLoader.setup(); + loadAllNodeApiSymbols(); + } +} + +fn nodeApiReturnType(comptime Fn: type) type { + return @typeInfo(@typeInfo(Fn).pointer.child).@"fn".return_type.?; +} + +fn missingNodeApiSymbol(comptime name: [:0]const u8, comptime Return: type) Return { + std.debug.print("Node-API symbol {s} has not been loaded\n", .{name}); + if (Return == napi_status) return napi_invalid_arg; + if (Return == void) return; + if (Return == noreturn) @panic("Node-API symbol has not been loaded"); + return std.mem.zeroes(Return); +} + +fn callNodeApi(comptime name: [:0]const u8, comptime Fn: type, args: anytype) nodeApiReturnType(Fn) { + if (use_windows_msvc_dynamic_symbols) { + const function = WindowsMsvcLoader.lookupCached(Fn, name) orelse return missingNodeApiSymbol(name, nodeApiReturnType(Fn)); + return @call(.auto, function, args); + } + + const function = @extern(Fn, .{ .name = name }); + return @call(.auto, function, args); +} + +fn loadNodeApi(comptime name: [:0]const u8, comptime wrapper: anytype) void { + const Fn = *const @TypeOf(wrapper); + _ = WindowsMsvcLoader.lookupCached(Fn, name); +} + +fn loadAllNodeApiSymbols() void { + if (!use_windows_msvc_dynamic_symbols) return; + + loadNodeApi("napi_get_last_error_info", napi_get_last_error_info); + loadNodeApi("napi_get_undefined", napi_get_undefined); + loadNodeApi("napi_get_null", napi_get_null); + loadNodeApi("napi_get_global", napi_get_global); + loadNodeApi("napi_get_boolean", napi_get_boolean); + loadNodeApi("napi_create_object", napi_create_object); + loadNodeApi("napi_create_array", napi_create_array); + loadNodeApi("napi_create_array_with_length", napi_create_array_with_length); + loadNodeApi("napi_create_double", napi_create_double); + loadNodeApi("napi_create_int32", napi_create_int32); + loadNodeApi("napi_create_uint32", napi_create_uint32); + loadNodeApi("napi_create_int64", napi_create_int64); + loadNodeApi("napi_create_string_latin1", napi_create_string_latin1); + loadNodeApi("napi_create_string_utf8", napi_create_string_utf8); + loadNodeApi("napi_create_string_utf16", napi_create_string_utf16); + loadNodeApi("napi_create_symbol", napi_create_symbol); + loadNodeApi("napi_create_function", napi_create_function); + loadNodeApi("napi_create_error", napi_create_error); + loadNodeApi("napi_create_type_error", napi_create_type_error); + loadNodeApi("napi_create_range_error", napi_create_range_error); + loadNodeApi("napi_typeof", napi_typeof); + loadNodeApi("napi_get_value_double", napi_get_value_double); + loadNodeApi("napi_get_value_int32", napi_get_value_int32); + loadNodeApi("napi_get_value_uint32", napi_get_value_uint32); + loadNodeApi("napi_get_value_int64", napi_get_value_int64); + loadNodeApi("napi_get_value_bool", napi_get_value_bool); + loadNodeApi("napi_get_value_string_latin1", napi_get_value_string_latin1); + loadNodeApi("napi_get_value_string_utf8", napi_get_value_string_utf8); + loadNodeApi("napi_get_value_string_utf16", napi_get_value_string_utf16); + loadNodeApi("napi_coerce_to_bool", napi_coerce_to_bool); + loadNodeApi("napi_coerce_to_number", napi_coerce_to_number); + loadNodeApi("napi_coerce_to_object", napi_coerce_to_object); + loadNodeApi("napi_coerce_to_string", napi_coerce_to_string); + loadNodeApi("napi_get_prototype", napi_get_prototype); + loadNodeApi("napi_get_property_names", napi_get_property_names); + loadNodeApi("napi_set_property", napi_set_property); + loadNodeApi("napi_has_property", napi_has_property); + loadNodeApi("napi_get_property", napi_get_property); + loadNodeApi("napi_delete_property", napi_delete_property); + loadNodeApi("napi_has_own_property", napi_has_own_property); + loadNodeApi("napi_set_named_property", napi_set_named_property); + loadNodeApi("napi_has_named_property", napi_has_named_property); + loadNodeApi("napi_get_named_property", napi_get_named_property); + loadNodeApi("napi_set_element", napi_set_element); + loadNodeApi("napi_has_element", napi_has_element); + loadNodeApi("napi_get_element", napi_get_element); + loadNodeApi("napi_delete_element", napi_delete_element); + loadNodeApi("napi_define_properties", napi_define_properties); + loadNodeApi("napi_is_array", napi_is_array); + loadNodeApi("napi_get_array_length", napi_get_array_length); + loadNodeApi("napi_strict_equals", napi_strict_equals); + loadNodeApi("napi_call_function", napi_call_function); + loadNodeApi("napi_new_instance", napi_new_instance); + loadNodeApi("napi_instanceof", napi_instanceof); + loadNodeApi("napi_get_cb_info", napi_get_cb_info); + loadNodeApi("napi_get_new_target", napi_get_new_target); + loadNodeApi("napi_define_class", napi_define_class); + loadNodeApi("napi_wrap", napi_wrap); + loadNodeApi("napi_unwrap", napi_unwrap); + loadNodeApi("napi_remove_wrap", napi_remove_wrap); + loadNodeApi("napi_create_external", napi_create_external); + loadNodeApi("napi_get_value_external", napi_get_value_external); + loadNodeApi("napi_create_reference", napi_create_reference); + loadNodeApi("napi_delete_reference", napi_delete_reference); + loadNodeApi("napi_reference_ref", napi_reference_ref); + loadNodeApi("napi_reference_unref", napi_reference_unref); + loadNodeApi("napi_get_reference_value", napi_get_reference_value); + loadNodeApi("napi_open_handle_scope", napi_open_handle_scope); + loadNodeApi("napi_close_handle_scope", napi_close_handle_scope); + loadNodeApi("napi_open_escapable_handle_scope", napi_open_escapable_handle_scope); + loadNodeApi("napi_close_escapable_handle_scope", napi_close_escapable_handle_scope); + loadNodeApi("napi_escape_handle", napi_escape_handle); + loadNodeApi("napi_throw", napi_throw); + loadNodeApi("napi_throw_error", napi_throw_error); + loadNodeApi("napi_throw_type_error", napi_throw_type_error); + loadNodeApi("napi_throw_range_error", napi_throw_range_error); + loadNodeApi("napi_is_error", napi_is_error); + loadNodeApi("napi_is_exception_pending", napi_is_exception_pending); + loadNodeApi("napi_get_and_clear_last_exception", napi_get_and_clear_last_exception); + loadNodeApi("napi_is_arraybuffer", napi_is_arraybuffer); + loadNodeApi("napi_create_arraybuffer", napi_create_arraybuffer); + loadNodeApi("napi_create_external_arraybuffer", napi_create_external_arraybuffer); + loadNodeApi("napi_get_arraybuffer_info", napi_get_arraybuffer_info); + loadNodeApi("napi_is_typedarray", napi_is_typedarray); + loadNodeApi("napi_create_typedarray", napi_create_typedarray); + loadNodeApi("napi_get_typedarray_info", napi_get_typedarray_info); + loadNodeApi("napi_create_dataview", napi_create_dataview); + loadNodeApi("napi_is_dataview", napi_is_dataview); + loadNodeApi("napi_get_dataview_info", napi_get_dataview_info); + loadNodeApi("napi_get_version", napi_get_version); + loadNodeApi("napi_create_promise", napi_create_promise); + loadNodeApi("napi_resolve_deferred", napi_resolve_deferred); + loadNodeApi("napi_reject_deferred", napi_reject_deferred); + loadNodeApi("napi_is_promise", napi_is_promise); + loadNodeApi("napi_run_script", napi_run_script); + loadNodeApi("napi_adjust_external_memory", napi_adjust_external_memory); + loadNodeApi("napi_module_register", napi_module_register); + loadNodeApi("napi_fatal_error", napi_fatal_error); + loadNodeApi("napi_async_init", napi_async_init); + loadNodeApi("napi_async_destroy", napi_async_destroy); + loadNodeApi("napi_make_callback", napi_make_callback); + loadNodeApi("napi_create_buffer", napi_create_buffer); + loadNodeApi("napi_create_external_buffer", napi_create_external_buffer); + loadNodeApi("napi_create_buffer_copy", napi_create_buffer_copy); + loadNodeApi("napi_is_buffer", napi_is_buffer); + loadNodeApi("napi_get_buffer_info", napi_get_buffer_info); + loadNodeApi("napi_create_async_work", napi_create_async_work); + loadNodeApi("napi_delete_async_work", napi_delete_async_work); + loadNodeApi("napi_queue_async_work", napi_queue_async_work); + loadNodeApi("napi_cancel_async_work", napi_cancel_async_work); + loadNodeApi("napi_get_node_version", napi_get_node_version); + loadNodeApi("napi_fatal_exception", napi_fatal_exception); + loadNodeApi("napi_add_env_cleanup_hook", napi_add_env_cleanup_hook); + loadNodeApi("napi_remove_env_cleanup_hook", napi_remove_env_cleanup_hook); + loadNodeApi("napi_open_callback_scope", napi_open_callback_scope); + loadNodeApi("napi_close_callback_scope", napi_close_callback_scope); + loadNodeApi("napi_create_threadsafe_function", napi_create_threadsafe_function); + loadNodeApi("napi_get_threadsafe_function_context", napi_get_threadsafe_function_context); + loadNodeApi("napi_call_threadsafe_function", napi_call_threadsafe_function); + loadNodeApi("napi_acquire_threadsafe_function", napi_acquire_threadsafe_function); + loadNodeApi("napi_release_threadsafe_function", napi_release_threadsafe_function); + loadNodeApi("napi_unref_threadsafe_function", napi_unref_threadsafe_function); + loadNodeApi("napi_ref_threadsafe_function", napi_ref_threadsafe_function); + loadNodeApi("napi_create_date", napi_create_date); + loadNodeApi("napi_is_date", napi_is_date); + loadNodeApi("napi_get_date_value", napi_get_date_value); + loadNodeApi("napi_add_finalizer", napi_add_finalizer); + loadNodeApi("napi_create_bigint_int64", napi_create_bigint_int64); + loadNodeApi("napi_create_bigint_uint64", napi_create_bigint_uint64); + loadNodeApi("napi_create_bigint_words", napi_create_bigint_words); + loadNodeApi("napi_get_value_bigint_int64", napi_get_value_bigint_int64); + loadNodeApi("napi_get_value_bigint_uint64", napi_get_value_bigint_uint64); + loadNodeApi("napi_get_value_bigint_words", napi_get_value_bigint_words); + loadNodeApi("napi_get_all_property_names", napi_get_all_property_names); + loadNodeApi("napi_set_instance_data", napi_set_instance_data); + loadNodeApi("napi_get_instance_data", napi_get_instance_data); + loadNodeApi("napi_detach_arraybuffer", napi_detach_arraybuffer); + loadNodeApi("napi_is_detached_arraybuffer", napi_is_detached_arraybuffer); + loadNodeApi("napi_type_tag_object", napi_type_tag_object); + loadNodeApi("napi_check_object_type_tag", napi_check_object_type_tag); + loadNodeApi("napi_object_freeze", napi_object_freeze); + loadNodeApi("napi_object_seal", napi_object_seal); + loadNodeApi("napi_add_async_cleanup_hook", napi_add_async_cleanup_hook); + loadNodeApi("napi_remove_async_cleanup_hook", napi_remove_async_cleanup_hook); + loadNodeApi("node_api_symbol_for", node_api_symbol_for); + loadNodeApi("node_api_create_syntax_error", node_api_create_syntax_error); + loadNodeApi("node_api_throw_syntax_error", node_api_throw_syntax_error); + loadNodeApi("node_api_get_module_file_name", node_api_get_module_file_name); + loadNodeApi("node_api_create_external_string_latin1", node_api_create_external_string_latin1); + loadNodeApi("node_api_create_external_string_utf16", node_api_create_external_string_utf16); + loadNodeApi("node_api_create_property_key_latin1", node_api_create_property_key_latin1); + loadNodeApi("node_api_create_property_key_utf8", node_api_create_property_key_utf8); + loadNodeApi("node_api_create_property_key_utf16", node_api_create_property_key_utf16); + loadNodeApi("node_api_create_buffer_from_arraybuffer", node_api_create_buffer_from_arraybuffer); + loadNodeApi("node_api_post_finalizer", node_api_post_finalizer); + loadNodeApi("napi_create_object_with_properties", napi_create_object_with_properties); +} +pub fn napi_get_last_error_info(arg0: node_api_basic_env, arg1: [*c][*c]const napi_extended_error_info) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, [*c][*c]const napi_extended_error_info) callconv(.c) napi_status; + return callNodeApi("napi_get_last_error_info", Fn, .{ arg0, arg1 }); +} +pub fn napi_get_undefined(arg0: napi_env, arg1: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_undefined", Fn, .{ arg0, arg1 }); +} +pub fn napi_get_null(arg0: napi_env, arg1: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_null", Fn, .{ arg0, arg1 }); +} +pub fn napi_get_global(arg0: napi_env, arg1: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_global", Fn, .{ arg0, arg1 }); +} +pub fn napi_get_boolean(arg0: napi_env, arg1: bool, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, bool, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_boolean", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_object(arg0: napi_env, arg1: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_object", Fn, .{ arg0, arg1 }); +} +pub fn napi_create_array(arg0: napi_env, arg1: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_array", Fn, .{ arg0, arg1 }); +} +pub fn napi_create_array_with_length(arg0: napi_env, arg1: usize, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_array_with_length", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_double(arg0: napi_env, arg1: f64, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, f64, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_double", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_int32(arg0: napi_env, arg1: i32, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, i32, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_int32", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_uint32(arg0: napi_env, arg1: u32, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, u32, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_uint32", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_int64(arg0: napi_env, arg1: i64, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, i64, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_int64", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_string_latin1(arg0: napi_env, arg1: [*c]const u8, arg2: usize, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_string_latin1", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_string_utf8(arg0: napi_env, arg1: [*c]const u8, arg2: usize, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_string_utf8", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_string_utf16(arg0: napi_env, arg1: [*c]const u16, arg2: usize, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u16, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_string_utf16", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_symbol(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_symbol", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_function(arg0: napi_env, arg1: [*c]const u8, arg2: usize, arg3: napi_callback, arg4: ?*anyopaque, arg5: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, usize, napi_callback, ?*anyopaque, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_function", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_create_error(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_error", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_type_error(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_type_error", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_range_error(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_range_error", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_typeof(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_valuetype) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_valuetype) callconv(.c) napi_status; + return callNodeApi("napi_typeof", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_value_double(arg0: napi_env, arg1: napi_value, arg2: [*c]f64) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]f64) callconv(.c) napi_status; + return callNodeApi("napi_get_value_double", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_value_int32(arg0: napi_env, arg1: napi_value, arg2: [*c]i32) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]i32) callconv(.c) napi_status; + return callNodeApi("napi_get_value_int32", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_value_uint32(arg0: napi_env, arg1: napi_value, arg2: [*c]u32) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]u32) callconv(.c) napi_status; + return callNodeApi("napi_get_value_uint32", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_value_int64(arg0: napi_env, arg1: napi_value, arg2: [*c]i64) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]i64) callconv(.c) napi_status; + return callNodeApi("napi_get_value_int64", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_value_bool(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_get_value_bool", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_value_string_latin1(arg0: napi_env, arg1: napi_value, arg2: [*c]u8, arg3: usize, arg4: ?*usize) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]u8, usize, ?*usize) callconv(.c) napi_status; + return callNodeApi("napi_get_value_string_latin1", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_get_value_string_utf8(arg0: napi_env, arg1: napi_value, arg2: [*c]u8, arg3: usize, arg4: ?*usize) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]u8, usize, ?*usize) callconv(.c) napi_status; + return callNodeApi("napi_get_value_string_utf8", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_get_value_string_utf16(arg0: napi_env, arg1: napi_value, arg2: [*c]u16, arg3: usize, arg4: ?*usize) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]u16, usize, ?*usize) callconv(.c) napi_status; + return callNodeApi("napi_get_value_string_utf16", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_coerce_to_bool(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_coerce_to_bool", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_coerce_to_number(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_coerce_to_number", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_coerce_to_object(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_coerce_to_object", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_coerce_to_string(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_coerce_to_string", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_prototype(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_prototype", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_property_names(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_property_names", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_set_property(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_set_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_has_property(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_has_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_get_property(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_delete_property(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_delete_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_has_own_property(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_has_own_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_set_named_property(arg0: napi_env, arg1: napi_value, arg2: [*c]const u8, arg3: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]const u8, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_set_named_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_has_named_property(arg0: napi_env, arg1: napi_value, arg2: [*c]const u8, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]const u8, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_has_named_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_get_named_property(arg0: napi_env, arg1: napi_value, arg2: [*c]const u8, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]const u8, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_named_property", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_set_element(arg0: napi_env, arg1: napi_value, arg2: u32, arg3: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, u32, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_set_element", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_has_element(arg0: napi_env, arg1: napi_value, arg2: u32, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, u32, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_has_element", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_get_element(arg0: napi_env, arg1: napi_value, arg2: u32, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, u32, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_element", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_delete_element(arg0: napi_env, arg1: napi_value, arg2: u32, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, u32, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_delete_element", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_define_properties(arg0: napi_env, arg1: napi_value, arg2: usize, arg3: [*c]const napi_property_descriptor) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, usize, [*c]const napi_property_descriptor) callconv(.c) napi_status; + return callNodeApi("napi_define_properties", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_is_array(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_array", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_array_length(arg0: napi_env, arg1: napi_value, arg2: [*c]u32) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]u32) callconv(.c) napi_status; + return callNodeApi("napi_get_array_length", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_strict_equals(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_strict_equals", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_call_function(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: usize, arg4: [*c]const napi_value, arg5: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, usize, [*c]const napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_call_function", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_new_instance(arg0: napi_env, arg1: napi_value, arg2: usize, arg3: [*c]const napi_value, arg4: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, usize, [*c]const napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_new_instance", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_instanceof(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_instanceof", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_get_cb_info(arg0: napi_env, arg1: napi_callback_info, arg2: ?*usize, arg3: [*c]napi_value, arg4: [*c]napi_value, arg5: [*c]?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_callback_info, ?*usize, [*c]napi_value, [*c]napi_value, [*c]?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_get_cb_info", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_get_new_target(arg0: napi_env, arg1: napi_callback_info, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_callback_info, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_new_target", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_define_class(arg0: napi_env, arg1: [*c]const u8, arg2: usize, arg3: napi_callback, arg4: ?*anyopaque, arg5: usize, arg6: [*c]const napi_property_descriptor, arg7: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, usize, napi_callback, ?*anyopaque, usize, [*c]const napi_property_descriptor, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_define_class", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 }); +} +pub fn napi_wrap(arg0: napi_env, arg1: napi_value, arg2: ?*anyopaque, arg3: node_api_basic_finalize, arg4: ?*anyopaque, arg5: [*c]napi_ref) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, ?*anyopaque, node_api_basic_finalize, ?*anyopaque, [*c]napi_ref) callconv(.c) napi_status; + return callNodeApi("napi_wrap", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_unwrap(arg0: napi_env, arg1: napi_value, arg2: [*c]?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_unwrap", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_remove_wrap(arg0: napi_env, arg1: napi_value, arg2: [*c]?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_remove_wrap", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_external(arg0: napi_env, arg1: ?*anyopaque, arg2: node_api_basic_finalize, arg3: ?*anyopaque, arg4: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, ?*anyopaque, node_api_basic_finalize, ?*anyopaque, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_external", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_get_value_external(arg0: napi_env, arg1: napi_value, arg2: [*c]?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_get_value_external", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_reference(arg0: napi_env, arg1: napi_value, arg2: u32, arg3: [*c]napi_ref) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, u32, [*c]napi_ref) callconv(.c) napi_status; + return callNodeApi("napi_create_reference", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_delete_reference(arg0: node_api_basic_env, arg1: napi_ref) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_ref) callconv(.c) napi_status; + return callNodeApi("napi_delete_reference", Fn, .{ arg0, arg1 }); +} +pub fn napi_reference_ref(arg0: napi_env, arg1: napi_ref, arg2: [*c]u32) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_ref, [*c]u32) callconv(.c) napi_status; + return callNodeApi("napi_reference_ref", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_reference_unref(arg0: napi_env, arg1: napi_ref, arg2: [*c]u32) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_ref, [*c]u32) callconv(.c) napi_status; + return callNodeApi("napi_reference_unref", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_reference_value(arg0: napi_env, arg1: napi_ref, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_ref, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_reference_value", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_open_handle_scope(arg0: napi_env, arg1: [*c]napi_handle_scope) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_handle_scope) callconv(.c) napi_status; + return callNodeApi("napi_open_handle_scope", Fn, .{ arg0, arg1 }); +} +pub fn napi_close_handle_scope(arg0: napi_env, arg1: napi_handle_scope) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_handle_scope) callconv(.c) napi_status; + return callNodeApi("napi_close_handle_scope", Fn, .{ arg0, arg1 }); +} +pub fn napi_open_escapable_handle_scope(arg0: napi_env, arg1: [*c]napi_escapable_handle_scope) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_escapable_handle_scope) callconv(.c) napi_status; + return callNodeApi("napi_open_escapable_handle_scope", Fn, .{ arg0, arg1 }); +} +pub fn napi_close_escapable_handle_scope(arg0: napi_env, arg1: napi_escapable_handle_scope) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_escapable_handle_scope) callconv(.c) napi_status; + return callNodeApi("napi_close_escapable_handle_scope", Fn, .{ arg0, arg1 }); +} +pub fn napi_escape_handle(arg0: napi_env, arg1: napi_escapable_handle_scope, arg2: napi_value, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_escapable_handle_scope, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_escape_handle", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_throw(arg0: napi_env, arg1: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_throw", Fn, .{ arg0, arg1 }); +} +pub fn napi_throw_error(arg0: napi_env, arg1: [*c]const u8, arg2: [*c]const u8) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, [*c]const u8) callconv(.c) napi_status; + return callNodeApi("napi_throw_error", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_throw_type_error(arg0: napi_env, arg1: [*c]const u8, arg2: [*c]const u8) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, [*c]const u8) callconv(.c) napi_status; + return callNodeApi("napi_throw_type_error", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_throw_range_error(arg0: napi_env, arg1: [*c]const u8, arg2: [*c]const u8) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, [*c]const u8) callconv(.c) napi_status; + return callNodeApi("napi_throw_range_error", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_is_error(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_error", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_is_exception_pending(arg0: napi_env, arg1: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_exception_pending", Fn, .{ arg0, arg1 }); +} +pub fn napi_get_and_clear_last_exception(arg0: napi_env, arg1: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_and_clear_last_exception", Fn, .{ arg0, arg1 }); +} +pub fn napi_is_arraybuffer(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_arraybuffer", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_arraybuffer(arg0: napi_env, arg1: usize, arg2: [*c]?*anyopaque, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, usize, [*c]?*anyopaque, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_arraybuffer", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_external_arraybuffer(arg0: napi_env, arg1: ?*anyopaque, arg2: usize, arg3: node_api_basic_finalize, arg4: ?*anyopaque, arg5: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, ?*anyopaque, usize, node_api_basic_finalize, ?*anyopaque, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_external_arraybuffer", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_get_arraybuffer_info(arg0: napi_env, arg1: napi_value, arg2: [*c]?*anyopaque, arg3: [*c]usize) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]?*anyopaque, [*c]usize) callconv(.c) napi_status; + return callNodeApi("napi_get_arraybuffer_info", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_is_typedarray(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_typedarray", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_typedarray(arg0: napi_env, arg1: napi_typedarray_type, arg2: usize, arg3: napi_value, arg4: usize, arg5: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_typedarray_type, usize, napi_value, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_typedarray", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_get_typedarray_info(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_typedarray_type, arg3: [*c]usize, arg4: [*c]?*anyopaque, arg5: [*c]napi_value, arg6: [*c]usize) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_typedarray_type, [*c]usize, [*c]?*anyopaque, [*c]napi_value, [*c]usize) callconv(.c) napi_status; + return callNodeApi("napi_get_typedarray_info", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5, arg6 }); +} +pub fn napi_create_dataview(arg0: napi_env, arg1: usize, arg2: napi_value, arg3: usize, arg4: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, usize, napi_value, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_dataview", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_is_dataview(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_dataview", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_dataview_info(arg0: napi_env, arg1: napi_value, arg2: [*c]usize, arg3: [*c]?*anyopaque, arg4: [*c]napi_value, arg5: [*c]usize) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]usize, [*c]?*anyopaque, [*c]napi_value, [*c]usize) callconv(.c) napi_status; + return callNodeApi("napi_get_dataview_info", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_get_version(arg0: node_api_basic_env, arg1: [*c]u32) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, [*c]u32) callconv(.c) napi_status; + return callNodeApi("napi_get_version", Fn, .{ arg0, arg1 }); +} +pub fn napi_create_promise(arg0: napi_env, arg1: [*c]napi_deferred, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]napi_deferred, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_promise", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_resolve_deferred(arg0: napi_env, arg1: napi_deferred, arg2: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_deferred, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_resolve_deferred", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_reject_deferred(arg0: napi_env, arg1: napi_deferred, arg2: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_deferred, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_reject_deferred", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_is_promise(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_promise", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_run_script(arg0: napi_env, arg1: napi_value, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_run_script", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_adjust_external_memory(arg0: node_api_basic_env, arg1: i64, arg2: [*c]i64) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, i64, [*c]i64) callconv(.c) napi_status; + return callNodeApi("napi_adjust_external_memory", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_module_register(arg0: [*c]napi_module) callconv(.c) void { + const Fn = *const fn ([*c]napi_module) callconv(.c) void; + return callNodeApi("napi_module_register", Fn, .{arg0}); +} +pub fn napi_fatal_error(arg0: [*c]const u8, arg1: usize, arg2: [*c]const u8, arg3: usize) callconv(.c) noreturn { + const Fn = *const fn ([*c]const u8, usize, [*c]const u8, usize) callconv(.c) noreturn; + return callNodeApi("napi_fatal_error", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_async_init(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]napi_async_context) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]napi_async_context) callconv(.c) napi_status; + return callNodeApi("napi_async_init", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_async_destroy(arg0: napi_env, arg1: napi_async_context) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_async_context) callconv(.c) napi_status; + return callNodeApi("napi_async_destroy", Fn, .{ arg0, arg1 }); +} +pub fn napi_make_callback(arg0: napi_env, arg1: napi_async_context, arg2: napi_value, arg3: napi_value, arg4: usize, arg5: [*c]const napi_value, arg6: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_async_context, napi_value, napi_value, usize, [*c]const napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_make_callback", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5, arg6 }); +} +pub fn napi_create_buffer(arg0: napi_env, arg1: usize, arg2: [*c]?*anyopaque, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, usize, [*c]?*anyopaque, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_buffer", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_external_buffer(arg0: napi_env, arg1: usize, arg2: ?*anyopaque, arg3: node_api_basic_finalize, arg4: ?*anyopaque, arg5: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, usize, ?*anyopaque, node_api_basic_finalize, ?*anyopaque, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_external_buffer", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_create_buffer_copy(arg0: napi_env, arg1: usize, arg2: ?*const anyopaque, arg3: [*c]?*anyopaque, arg4: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, usize, ?*const anyopaque, [*c]?*anyopaque, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_buffer_copy", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_is_buffer(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_buffer", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_buffer_info(arg0: napi_env, arg1: napi_value, arg2: [*c]?*anyopaque, arg3: [*c]usize) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]?*anyopaque, [*c]usize) callconv(.c) napi_status; + return callNodeApi("napi_get_buffer_info", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_async_work(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: napi_async_execute_callback, arg4: napi_async_complete_callback, arg5: ?*anyopaque, arg6: [*c]napi_async_work) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, napi_async_execute_callback, napi_async_complete_callback, ?*anyopaque, [*c]napi_async_work) callconv(.c) napi_status; + return callNodeApi("napi_create_async_work", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5, arg6 }); +} +pub fn napi_delete_async_work(arg0: napi_env, arg1: napi_async_work) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_async_work) callconv(.c) napi_status; + return callNodeApi("napi_delete_async_work", Fn, .{ arg0, arg1 }); +} +pub fn napi_queue_async_work(arg0: node_api_basic_env, arg1: napi_async_work) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_async_work) callconv(.c) napi_status; + return callNodeApi("napi_queue_async_work", Fn, .{ arg0, arg1 }); +} +pub fn napi_cancel_async_work(arg0: node_api_basic_env, arg1: napi_async_work) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_async_work) callconv(.c) napi_status; + return callNodeApi("napi_cancel_async_work", Fn, .{ arg0, arg1 }); +} +pub fn napi_get_node_version(arg0: node_api_basic_env, arg1: [*c][*c]const napi_node_version) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, [*c][*c]const napi_node_version) callconv(.c) napi_status; + return callNodeApi("napi_get_node_version", Fn, .{ arg0, arg1 }); +} + +pub fn napi_fatal_exception(arg0: napi_env, arg1: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_fatal_exception", Fn, .{ arg0, arg1 }); +} +pub fn napi_add_env_cleanup_hook(arg0: node_api_basic_env, arg1: napi_cleanup_hook, arg2: ?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_cleanup_hook, ?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_add_env_cleanup_hook", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_remove_env_cleanup_hook(arg0: node_api_basic_env, arg1: napi_cleanup_hook, arg2: ?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_cleanup_hook, ?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_remove_env_cleanup_hook", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_open_callback_scope(arg0: napi_env, arg1: napi_value, arg2: napi_async_context, arg3: [*c]napi_callback_scope) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_async_context, [*c]napi_callback_scope) callconv(.c) napi_status; + return callNodeApi("napi_open_callback_scope", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_close_callback_scope(arg0: napi_env, arg1: napi_callback_scope) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_callback_scope) callconv(.c) napi_status; + return callNodeApi("napi_close_callback_scope", Fn, .{ arg0, arg1 }); +} + +pub const napi_threadsafe_function__ = opaque {}; +pub const napi_threadsafe_function = ?*napi_threadsafe_function__; +pub const napi_threadsafe_function_release_mode = c_int; +pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0; +pub const napi_tsfn_abort: napi_threadsafe_function_release_mode = 1; +pub const napi_threadsafe_function_call_mode = c_int; +pub const napi_tsfn_nonblocking: napi_threadsafe_function_call_mode = 0; +pub const napi_tsfn_blocking: napi_threadsafe_function_call_mode = 1; +pub const napi_threadsafe_function_call_js = ?*const fn (env: napi_env, js_callback: napi_value, context: ?*anyopaque, data: ?*anyopaque) callconv(.c) void; + +pub fn napi_create_threadsafe_function(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: napi_value, arg4: usize, arg5: usize, arg6: ?*anyopaque, arg7: napi_finalize, arg8: ?*anyopaque, arg9: napi_threadsafe_function_call_js, arg10: [*c]napi_threadsafe_function) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, napi_value, usize, usize, ?*anyopaque, napi_finalize, ?*anyopaque, napi_threadsafe_function_call_js, [*c]napi_threadsafe_function) callconv(.c) napi_status; + return callNodeApi("napi_create_threadsafe_function", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 }); +} +pub fn napi_get_threadsafe_function_context(arg0: napi_threadsafe_function, arg1: [*c]?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (napi_threadsafe_function, [*c]?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_get_threadsafe_function_context", Fn, .{ arg0, arg1 }); +} +pub fn napi_call_threadsafe_function(arg0: napi_threadsafe_function, arg1: ?*anyopaque, arg2: napi_threadsafe_function_call_mode) callconv(.c) napi_status { + const Fn = *const fn (napi_threadsafe_function, ?*anyopaque, napi_threadsafe_function_call_mode) callconv(.c) napi_status; + return callNodeApi("napi_call_threadsafe_function", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_acquire_threadsafe_function(arg0: napi_threadsafe_function) callconv(.c) napi_status { + const Fn = *const fn (napi_threadsafe_function) callconv(.c) napi_status; + return callNodeApi("napi_acquire_threadsafe_function", Fn, .{arg0}); +} +pub fn napi_release_threadsafe_function(arg0: napi_threadsafe_function, arg1: napi_threadsafe_function_release_mode) callconv(.c) napi_status { + const Fn = *const fn (napi_threadsafe_function, napi_threadsafe_function_release_mode) callconv(.c) napi_status; + return callNodeApi("napi_release_threadsafe_function", Fn, .{ arg0, arg1 }); +} +pub fn napi_unref_threadsafe_function(arg0: node_api_basic_env, arg1: napi_threadsafe_function) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_threadsafe_function) callconv(.c) napi_status; + return callNodeApi("napi_unref_threadsafe_function", Fn, .{ arg0, arg1 }); +} +pub fn napi_ref_threadsafe_function(arg0: node_api_basic_env, arg1: napi_threadsafe_function) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_threadsafe_function) callconv(.c) napi_status; + return callNodeApi("napi_ref_threadsafe_function", Fn, .{ arg0, arg1 }); +} + +pub fn napi_create_date(arg0: napi_env, arg1: f64, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, f64, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_date", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_is_date(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_date", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_get_date_value(arg0: napi_env, arg1: napi_value, arg2: [*c]f64) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]f64) callconv(.c) napi_status; + return callNodeApi("napi_get_date_value", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_add_finalizer(arg0: napi_env, arg1: napi_value, arg2: ?*anyopaque, arg3: node_api_basic_finalize, arg4: ?*anyopaque, arg5: [*c]napi_ref) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, ?*anyopaque, node_api_basic_finalize, ?*anyopaque, [*c]napi_ref) callconv(.c) napi_status; + return callNodeApi("napi_add_finalizer", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} + +pub const napi_bigint: napi_valuetype = 9; +pub const napi_bigint64_array: napi_typedarray_type = 9; +pub const napi_biguint64_array: napi_typedarray_type = 10; +pub const napi_key_collection_mode = c_int; +pub const napi_key_include_prototypes: napi_key_collection_mode = 0; +pub const napi_key_own_only: napi_key_collection_mode = 1; +pub const napi_key_filter = c_int; +pub const napi_key_all_properties: napi_key_filter = 0; +pub const napi_key_writable: napi_key_filter = 1; +pub const napi_key_enumerable: napi_key_filter = 1 << 1; +pub const napi_key_configurable: napi_key_filter = 1 << 2; +pub const napi_key_skip_strings: napi_key_filter = 1 << 3; +pub const napi_key_skip_symbols: napi_key_filter = 1 << 4; +pub const napi_key_conversion = c_int; +pub const napi_key_keep_numbers: napi_key_conversion = 0; +pub const napi_key_numbers_to_strings: napi_key_conversion = 1; + +pub fn napi_create_bigint_int64(arg0: napi_env, arg1: i64, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, i64, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_bigint_int64", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_bigint_uint64(arg0: napi_env, arg1: u64, arg2: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, u64, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_bigint_uint64", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_create_bigint_words(arg0: napi_env, arg1: c_int, arg2: usize, arg3: [*c]const u64, arg4: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, c_int, usize, [*c]const u64, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_bigint_words", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_get_value_bigint_int64(arg0: napi_env, arg1: napi_value, arg2: [*c]i64, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]i64, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_get_value_bigint_int64", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_get_value_bigint_uint64(arg0: napi_env, arg1: napi_value, arg2: [*c]u64, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]u64, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_get_value_bigint_uint64", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_get_value_bigint_words(arg0: napi_env, arg1: napi_value, arg2: [*c]c_int, arg3: [*c]usize, arg4: [*c]u64) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]c_int, [*c]usize, [*c]u64) callconv(.c) napi_status; + return callNodeApi("napi_get_value_bigint_words", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} +pub fn napi_get_all_property_names(arg0: napi_env, arg1: napi_value, arg2: napi_key_collection_mode, arg3: napi_key_filter, arg4: napi_key_conversion, arg5: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_key_collection_mode, napi_key_filter, napi_key_conversion, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_get_all_property_names", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} +pub fn napi_set_instance_data(arg0: node_api_basic_env, arg1: ?*anyopaque, arg2: napi_finalize, arg3: ?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, ?*anyopaque, napi_finalize, ?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_set_instance_data", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_get_instance_data(arg0: node_api_basic_env, arg1: [*c]?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, [*c]?*anyopaque) callconv(.c) napi_status; + return callNodeApi("napi_get_instance_data", Fn, .{ arg0, arg1 }); +} + +pub fn napi_detach_arraybuffer(arg0: napi_env, arg1: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_detach_arraybuffer", Fn, .{ arg0, arg1 }); +} +pub fn napi_is_detached_arraybuffer(arg0: napi_env, arg1: napi_value, arg2: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_is_detached_arraybuffer", Fn, .{ arg0, arg1, arg2 }); +} + +pub const napi_type_tag = extern struct { + lower: u64, + upper: u64, +}; +pub const napi_async_cleanup_hook_handle__ = opaque {}; +pub const napi_async_cleanup_hook_handle = ?*napi_async_cleanup_hook_handle__; +pub const napi_async_cleanup_hook = ?*const fn (handle: napi_async_cleanup_hook_handle, data: ?*anyopaque) callconv(.c) void; + +pub fn napi_type_tag_object(arg0: napi_env, arg1: napi_value, arg2: [*c]const napi_type_tag) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]const napi_type_tag) callconv(.c) napi_status; + return callNodeApi("napi_type_tag_object", Fn, .{ arg0, arg1, arg2 }); +} +pub fn napi_check_object_type_tag(arg0: napi_env, arg1: napi_value, arg2: [*c]const napi_type_tag, arg3: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]const napi_type_tag, [*c]bool) callconv(.c) napi_status; + return callNodeApi("napi_check_object_type_tag", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_object_freeze(arg0: napi_env, arg1: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_object_freeze", Fn, .{ arg0, arg1 }); +} +pub fn napi_object_seal(arg0: napi_env, arg1: napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value) callconv(.c) napi_status; + return callNodeApi("napi_object_seal", Fn, .{ arg0, arg1 }); +} +pub fn napi_add_async_cleanup_hook(arg0: node_api_basic_env, arg1: napi_async_cleanup_hook, arg2: ?*anyopaque, arg3: [*c]napi_async_cleanup_hook_handle) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_async_cleanup_hook, ?*anyopaque, [*c]napi_async_cleanup_hook_handle) callconv(.c) napi_status; + return callNodeApi("napi_add_async_cleanup_hook", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_remove_async_cleanup_hook(arg0: napi_async_cleanup_hook_handle) callconv(.c) napi_status { + const Fn = *const fn (napi_async_cleanup_hook_handle) callconv(.c) napi_status; + return callNodeApi("napi_remove_async_cleanup_hook", Fn, .{arg0}); +} + +pub fn node_api_symbol_for(arg0: napi_env, arg1: [*c]const u8, arg2: usize, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("node_api_symbol_for", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn node_api_create_syntax_error(arg0: napi_env, arg1: napi_value, arg2: napi_value, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, napi_value, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("node_api_create_syntax_error", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn node_api_throw_syntax_error(arg0: napi_env, arg1: [*c]const u8, arg2: [*c]const u8) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, [*c]const u8) callconv(.c) napi_status; + return callNodeApi("node_api_throw_syntax_error", Fn, .{ arg0, arg1, arg2 }); +} +pub fn node_api_get_module_file_name(arg0: node_api_basic_env, arg1: [*c][*c]const u8) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, [*c][*c]const u8) callconv(.c) napi_status; + return callNodeApi("node_api_get_module_file_name", Fn, .{ arg0, arg1 }); +} + +pub fn node_api_create_external_string_latin1(arg0: napi_env, arg1: [*c]u8, arg2: usize, arg3: node_api_basic_finalize, arg4: ?*anyopaque, arg5: [*c]napi_value, arg6: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]u8, usize, node_api_basic_finalize, ?*anyopaque, [*c]napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("node_api_create_external_string_latin1", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5, arg6 }); +} +pub fn node_api_create_external_string_utf16(arg0: napi_env, arg1: [*c]u16, arg2: usize, arg3: node_api_basic_finalize, arg4: ?*anyopaque, arg5: [*c]napi_value, arg6: [*c]bool) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]u16, usize, node_api_basic_finalize, ?*anyopaque, [*c]napi_value, [*c]bool) callconv(.c) napi_status; + return callNodeApi("node_api_create_external_string_utf16", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5, arg6 }); +} +pub fn node_api_create_property_key_latin1(arg0: napi_env, arg1: [*c]const u8, arg2: usize, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("node_api_create_property_key_latin1", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn node_api_create_property_key_utf8(arg0: napi_env, arg1: [*c]const u8, arg2: usize, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u8, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("node_api_create_property_key_utf8", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn node_api_create_property_key_utf16(arg0: napi_env, arg1: [*c]const u16, arg2: usize, arg3: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, [*c]const u16, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("node_api_create_property_key_utf16", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn node_api_create_buffer_from_arraybuffer(arg0: napi_env, arg1: napi_value, arg2: usize, arg3: usize, arg4: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, usize, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("node_api_create_buffer_from_arraybuffer", Fn, .{ arg0, arg1, arg2, arg3, arg4 }); +} + +pub fn node_api_post_finalizer(arg0: node_api_basic_env, arg1: napi_finalize, arg2: ?*anyopaque, arg3: ?*anyopaque) callconv(.c) napi_status { + const Fn = *const fn (node_api_basic_env, napi_finalize, ?*anyopaque, ?*anyopaque) callconv(.c) napi_status; + return callNodeApi("node_api_post_finalizer", Fn, .{ arg0, arg1, arg2, arg3 }); +} +pub fn napi_create_object_with_properties(arg0: napi_env, arg1: napi_value, arg2: [*c]const napi_value, arg3: [*c]const napi_value, arg4: usize, arg5: [*c]napi_value) callconv(.c) napi_status { + const Fn = *const fn (napi_env, napi_value, [*c]const napi_value, [*c]const napi_value, usize, [*c]napi_value) callconv(.c) napi_status; + return callNodeApi("napi_create_object_with_properties", Fn, .{ arg0, arg1, arg2, arg3, arg4, arg5 }); +} diff --git a/src/sys/header/common.h b/src/sys/ohos/common.h similarity index 100% rename from src/sys/header/common.h rename to src/sys/ohos/common.h diff --git a/src/sys/header/js_native_api.h b/src/sys/ohos/js_native_api.h similarity index 100% rename from src/sys/header/js_native_api.h rename to src/sys/ohos/js_native_api.h diff --git a/src/sys/header/js_native_api_types.h b/src/sys/ohos/js_native_api_types.h similarity index 100% rename from src/sys/header/js_native_api_types.h rename to src/sys/ohos/js_native_api_types.h diff --git a/src/sys/header/native_api.h b/src/sys/ohos/native_api.h similarity index 100% rename from src/sys/header/native_api.h rename to src/sys/ohos/native_api.h diff --git a/src/sys/header/node_api.h b/src/sys/ohos/node_api.h similarity index 100% rename from src/sys/header/node_api.h rename to src/sys/ohos/node_api.h diff --git a/src/sys/header/node_api_types.h b/src/sys/ohos/node_api_types.h similarity index 100% rename from src/sys/header/node_api_types.h rename to src/sys/ohos/node_api_types.h diff --git a/test/binary.spec.ts b/test/binary.spec.ts index f122f86..be99cd5 100644 --- a/test/binary.spec.ts +++ b/test/binary.spec.ts @@ -6,6 +6,13 @@ export function testBinary(native: NativeAddon) { const bufferValue = native.create_buffer(); assertEqual(native.get_buffer(bufferValue), 1024, "buffer length"); assertEqual(native.get_buffer_as_string(bufferValue).length, 1024, "buffer string length"); + assertEqual(native.get_buffer(native.create_empty_buffer_new()), 0, "empty buffer new length"); + assertEqual(native.get_buffer(native.create_empty_buffer_copy()), 0, "empty buffer copy length"); + assertEqual( + native.get_buffer(native.create_empty_external_buffer()), + 0, + "empty external buffer length", + ); const arrayBufferValue = native.create_arraybuffer(); assertEqual(native.get_arraybuffer(arrayBufferValue), 1024, "arraybuffer length"); @@ -14,6 +21,21 @@ export function testBinary(native: NativeAddon) { 1024, "arraybuffer string length", ); + assertEqual( + native.get_arraybuffer(native.create_empty_arraybuffer_new()), + 0, + "empty arraybuffer new length", + ); + assertEqual( + native.get_arraybuffer(native.create_empty_arraybuffer_copy()), + 0, + "empty arraybuffer copy length", + ); + assertEqual( + native.get_arraybuffer(native.create_empty_external_arraybuffer()), + 0, + "empty external arraybuffer length", + ); const typedArrayValue = native.create_uint8_typedarray(); assertEqual(native.get_uint8_typedarray_length(typedArrayValue), 4, "typedarray length");