node-addon-api

repository·main·Indexed 25 days ago

https://github.com/nodejs/node-addon-api

A collection of header-only C++ wrapper classes for the C-based Node-API. It provides a C++ object model and exception handling with minimal overhead, allowing developers to build ABI-stable Node.js addons that are insulated from changes in the underlying JavaScript engine. Version 8.9.0 supports Node.js 18.x and newer.

Tokens
46.1K
Snippets
96
Records
250
Agent score
80%

What's inside node-addon-api

  1. What is node-addon-api

    main
    node-addon-api is a collection of header-only C++ wrapper classes designed to simplify the use of the C-based Node-API when developing Node.js addons in C++. It provides a C++ object model and exception handling semantics with low overhead, making it easier to write type-safe and idiomatic C++ code for Node.js extensions.
  2. What is Napi::ObjectReference and how does it work?

    main

    Napi::ObjectReference is a subclass of Napi::Reference that specifically holds a Napi::Object. It maintains a reference count for the held object; as long as this count is greater than 0, the object is protected from garbage collection.

    Unlike a standard Napi::Reference, an ObjectReference allows you to directly Set and Get properties on the underlying object through the reference itself.

    #include <napi.h>
    
    using namespace Napi;
    
    void Init(Env env) {
        // Create an empty ObjectReference that has an initial reference count of 2.
        ObjectReference obj_ref = Reference<Object>::New(Object::New(env), 2);
    
        // Set a couple of different properties on the reference.
        obj_ref.Set("hello", String::New(env, "world"));
        obj_ref.Set(42, "The Answer to Life, the Universe, and Everything");
    
        // Get the properties using the keys.
        Value val1 = obj_ref.Get("hello");
        Value val2 = obj_ref.Get(42);
    }
  3. What is Napi::FunctionReference and how does it work?

    main

    A Napi::FunctionReference is a subclass of Napi::Reference that acts as a wrapper for a Napi::Function. It maintains a reference count for the underlying JavaScript function.

    As long as the reference count is greater than 0, the function is protected from garbage collection, ensuring it remains accessible to your native add-on even if the original JavaScript reference is lost.

    It provides two primary ways to execute the function:

    1. Call: Used for synchronous execution.
    2. MakeCallback: Used for asynchronous execution (after an async operation).

    Refer to Napi::Function documentation to decide which method is appropriate for your use case.

  4. What is Napi::BasicEnv and when to use it

    main
    The Napi::BasicEnv object represents an environment with a limited subset of APIs compared to Napi::Env. It is primarily used in basic finalizers where a full Napi::Env might not be available or safe to use. This object is typically created and passed by the Node.js runtime or the node-addon-api infrastructure itself.
  5. What is node-addon-api and how does it provide ABI stability?

    main

    node-addon-api is a C++ wrapper for Node-API, which is an ABI-stable C interface provided by Node.js for building native addons.

    By using node-addon-api, your native modules are insulated from changes in the underlying JavaScript engine (like V8 or ChakraCore). This allows modules compiled for one version of Node.js to run on later versions without requiring recompilation, provided they rely exclusively on the stable Node-API surface.

    Warning: Using other Node.js interfaces like libuv (e.g., #include <uv.h>) directly can break ABI stability across Node.js major versions. To maintain stability, use node-addon-api exclusively.

  6. How Napi::AsyncWorker works

    main

    Overview

    Napi::AsyncWorker is an abstract class used to move data and perform tasks between the Node.js event loop and worker threads. It handles the complexities of thread management and asynchronous execution.

    Lifecycle

    1. Creation: Subclass Napi::AsyncWorker and provide a callback function (which runs on the main thread).
    2. Queueing: Call Napi::AsyncWorker::Queue() to request execution.
    3. Execution: When a thread is available, Napi::AsyncWorker::Execute() is invoked on a libuv worker thread.
      • CRITICAL: You must NOT call any node-addon-api methods or any code that invokes JavaScript inside Execute(), as it is not running on the main event loop.
    4. Completion: Once Execute() finishes, either Napi::AsyncWorker::OnOK() or Napi::AsyncWorker::OnError() is invoked on the main thread.
    5. Destruction: After the completion methods finish, the Napi::AsyncWorker instance is automatically destructed (unless SuppressDestruct() was called).
  7. Handle errors with C++ exceptions enabled

    main

    When C++ exceptions are enabled, Napi::Error extends std::exception. This allows you to use standard try/catch blocks to manage both C++ and JavaScript exceptions.

    • Automatic Conversion: If a Node-API call fails (e.g., invalid arguments) or a JavaScript function called from C++ throws, node-addon-api automatically converts these into Napi::Error C++ exceptions.
    • Propagation: If an Napi::Error escapes a native callback, it is automatically converted into a JavaScript exception when returning to the JS environment.
    • Dependency Behavior:
      • Using node_addon_api_except: Only Napi::Error objects are handled.
      • Using node_addon_api_except_all: All exceptions are handled. std::exception derivatives use their what() message; others use a generic message.
    // Throwing a C++ exception
    Env env = ...
    throw Napi::Error::New(env, "Example exception");
    
    // Propagating a Node-API C++ exception (JS function call)
    Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
    Napi::Value result = jsFunctionThatThrows({ arg1, arg2 });
    
    // Handling a Node-API C++ exception
    try {
        result = jsFunctionThatThrows({ arg1, arg2 });
    } catch (const Error& e) {
        cerr << "Caught JavaScript exception: " + e.what();
    }
  8. Handle empty Napi::Value and uninitialized states

    main

    An "empty" Napi::Value is an uninitialized instance created via the default constructor Napi::Value::Value().

    • IsEmpty(): Returns true if the value is uninitialized.
    • Warning: An empty Napi::Value is invalid. Most operations on an empty value will result in an exception.
    • Distinction: An empty Napi::Value is not the same as JavaScript null or undefined (which are valid values).
    • Exception Handling: When C++ exceptions are disabled, methods returning Napi::Value might return an empty Napi::Value to signal a pending exception. In this case, you must check Env::IsExceptionPending before proceeding.
  9. How Napi::AsyncProgressWorker works

    main

    Concept

    Napi::AsyncProgressWorker is an abstract class used to perform heavy tasks on a libuv worker thread while providing a mechanism to report progress back to the JavaScript event loop. It extends Napi::AsyncWorker by internally using Napi::ThreadSafeFunction to bridge the worker thread and the main event loop.

    Lifecycle

    1. Creation: Instantiate a subclass of Napi::AsyncProgressWorker.
    2. Queueing: Call Napi::AsyncProgressWorker::Queue to request execution.
    3. Execution: The Execute method runs on a background thread. During this phase, you can call ExecutionProgress::Send to report progress.
    4. Progress Reporting: ExecutionProgress::Send triggers OnProgress on the main JavaScript thread. Note that multiple Send calls might be coalesced into a single OnProgress invocation.
    5. Completion: Once Execute finishes, either OnOK or OnError is invoked on the main thread.
    6. Destruction: The instance is destructed after OnOK or OnError completes.

    Implementation Requirements

    For a basic implementation, you must override:

    • Execute(const ExecutionProgress& progress): The background task logic.
    • OnProgress(const T* data, size_t count): The logic to handle progress updates on the main thread.

    Warning: Inside Execute, you must not call any node-addon-api methods or any code that invokes JavaScript, as it runs on a thread other than the main event loop. Use OnOK or OnError for JavaScript interactions after the task completes.

  10. Use Napi::SharedArrayBuffer

    main

    The Napi::SharedArrayBuffer class provides a C++ wrapper for the JavaScript SharedArrayBuffer class. It allows you to allocate shared memory that can be accessed by multiple threads or workers.

    IMPORTANT: Support for Napi::SharedArrayBuffer is currently experimental. You must use the NAPI_EXPERIMENTAL macro and build against Node.js headers that support this feature.

  11. Use the Napi::TypedArray class

    main
    The Napi::TypedArray class is a C++ wrapper for the JavaScript TypedArray class. It inherits from Napi::Object. You can use it to inspect and interact with typed arrays (like Uint8Array, Float64Array, etc.) passed from JavaScript to your Node.js addon.
  12. Iterate over Napi::Object properties

    main

    If C++ exceptions are enabled (NAPI_CPP_EXCEPTIONS), you can iterate over an object's enumerable properties using iterators. Iterators yield an std::pair where .first is the Napi::Value key and .second is a Napi::Object::PropertyLValue value.

    Constant Iterator (const_iterator)

    • Iterated values are immutable.
    • Use for read-only traversal.

    Non-Constant Iterator (iterator)

    • Iterated values are mutable.
    • Allows you to modify the values of properties during iteration.

    Example: Summing numeric values in an object

    Value Sum(const CallbackInfo& info) {
      Object object = info[0].As<Object>();
      int64_t sum = 0;
    
      for (const auto& e : object) {
        sum += static_cast<Value>(e.second).As<Number>().Int64Value();
      }
    
      return Number::New(info.Env(), sum);
    }

    Example: Incrementing values in an object

    void Increment(const CallbackInfo& info) {
      Env env = info.Env();
      Object object = info[0].As<Object>();
    
      for (auto e : object) {
        int64_t value = static_cast<Value>(e.second).As<Number>().Int64Value();
        ++value;
        e.second = Napi::Number::New(env, value);
      }
    }