1.2.0 • Published 2 years ago

@markusjx/n-api-tools v1.2.0

Weekly downloads
60
License
MIT
Repository
github
Last release
2 years ago

n-api-tools

A toolbox containing C++ classes to be used with node-addon-api.

Installation

npm install @markusjx/n-api-tools

Usage

First, include the file in your project. CMake example:

execute_process(COMMAND node -p "require('@markusjx/n-api-tools').include"
                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
                OUTPUT_VARIABLE N_API_TOOLS_DIR)

# Include the directory
include_directories(${N_API_TOOLS_DIR})

This library is header-only, just include it:

#include <napi_tools.hpp>

Promises

Void promises

Napi::Promise returnsPromise(const Napi::CallbackInfo &info) {
    // Return a promise
    return napi_tools::promises::promise<void>(info.Env(), [] {
        // Sleep to pretend we're actually doing work
        std::this_thread::sleep_for(std::chrono::seconds(2));
    });
}

Non-void promises

Non-void promises support most basic types as numbers, strings, vectors or booleans.

Napi::Promise returnsPromise(const Napi::CallbackInfo &info) {
    // Return a promise
    return napi_tools::promises::promise<std::string>(info.Env(), [] {
        // Sleep to pretend we're actually doing work
        std::this_thread::sleep_for(std::chrono::seconds(2));

        // Return a string value
        return "some string";
    });
}

Callbacks

Callbacks can be used to call javascript function even without supplying a Napi::Env. The napi_tools::callbacks::callback takes function-like template arguments, e.g. <void()> or <int(std::string, float)>

Void callbacks

Create a callback with no arguments:

// Create a void callback
napi_tools::callbacks::callback<void()> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] should be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    callback = callbacks::callback<void()>(info);
}

// Call a callback function
void callCallback() {
    // Call the function async
    callback();
}

Create a callback with arguments:

// Create a void callback with arguments
napi_tools::callbacks::callback<void(std::string, int)> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] should be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    callback = callbacks::callback<void(std::string, int)>(info);
}

// Call a callback function
void callCallback() {
    // Call the function async and supply some arguments
    callback("some string", 42);
}

Non-void callbacks

Non-void callbacks return a std::promise to get a std::future from to get the result of the callback. NOTE: If you call std::future<T>::wait() in the main thread, you will create a deadlock as the callback cannot be called since the main thread is waiting for the callback to be called. To call a callback function from node.js use a napi_tools::promises::Promise<T> or some other technique to not block the main thread. You can also supply a callback function to be called with the function return type.

Create a callback with no arguments:

// Create a callback returning a number
napi_tools::callbacks::callback<int()> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] should be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    callback = callbacks::callback<int()>(info);
}

// Call a callback function.
void callCallback() {
    // Call the function async. It returns a std::promise
    std::promise<int> promise = callback();

    // Get the future
    std::future<int> future = promise.get_future();

    // Wait for the future to be resolved and get the result
    future.wait();
    int res = future.get();
}

Create a callback with no arguments and without std::future:

// Create a callback returning a number
napi_tools::callbacks::callback<int()> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] should be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    callback = callbacks::callback<int()>(info);
}

// Call a callback function.
void callCallback() {
    // Call the callback and supply a callback function
    callback([] (int res) {
        // The lambde will be called when the callback function finished.
        // The result of the callback will be stored in res.
    });
}

Create a callback with arguments:

// Create a callback returning a number
napi_tools::callbacks::callback<int(std::string, int)> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] should be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    callback = callbacks::callback<int(std::string, int)>(info);
}

// Call a callback function.
void callCallback() {
    // Call the function async. It returns a std::promise
    std::shared_ptr<std::promise<int>> promise = callback("some string", 42);

    // Get the future
    std::future<int> future = promise->get_future();

    // Wait for the future to be resolved and get the result
    future.wait();
    int res = future.get();
}

Create a callback with arguments and without std::future:

// Create a callback returning a number
napi_tools::callbacks::callback<int(std::string, int)> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] should be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    callback = callbacks::callback<int(std::string, int)>(info);
}

// Call a callback function.
void callCallback() {
    // Call the function and supply a callback function
    callback("some string", 42, [] (int res) {
        // The result will be stored in res
    });
}

Create a callback and wait for the js function to finish:

// Create a callback returning a number
napi_tools::callbacks::callback<int(std::string, int)> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] should be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    callback = callbacks::callback<int(std::string, int)>(info);
}

// Call a callback function.
void callCallback() {
    // Call the sync version of the function
    int result = callback.callSync("some string", 42);
}

Supply a conversion function with the callback:

// Create a callback returning a number
napi_tools::callbacks::callback<int(std::string, int)> callback = nullptr;

// Create a callback setter.
// This function should be exported and called by javascript.
// info[0] may be a Napi::Function.
void setCallback(const Napi::CallbackInfo &info) {
    // Create a conversion function.
    // The conversion function must return std::vector<napi_value>
    const auto converter = [] (std::string, int) -> std::vector<napi_value> {
        // Convert the values
    };

    // Set the callback and set the environment and the function manually.
    // Aditionally, supply the custom conversion function
    callback = callbacks::callback<int(std::string, int)>(info.Env(),
                                                          info[0].As<Napi::Function(),
                                                          converter);
}

Custom classes/structs as arguments/return types

In order to pass custom classes or structs to node.js or receive them, your class or struct must implement the static Napi::Value toNapiValue(Napi::Env, T) function to pass the class as an argument and the static T fromNapiValue(Napi::Value) function to get the class returned from the javascript process.

A custom implementation may look like this:

class custom_class {
public:
    // Some attributes
    std::string s1, s2;
    int i;
    std::vector<int> ints;

    // The toNapiValue function
    static Napi::Value toNapiValue(const Napi::Env &env, const custom_class &c) {
        Napi::Object obj = Napi::Object::New(env);

        // Convert the attributes using Napi::Value::From
        obj.Set("s1", Napi::Value::From(env, c.s1));
        obj.Set("s2", Napi::Value::From(env, c.s1));
        obj.Set("i",  Napi::Value::From(env, c.i));

        // You may also use the included conversion functions.
        // These can handle most basic types as strings, integers,
        // but also std::vector and std::map, plus classes with
        // the toNapiValue and/or fromNapiValue function(s)
        obj.Set("ints", napi_tools::util::conversions::cppValToValue(env, ints));
        return obj;
    }

    // The fromNapiValue function
    static custom_class fromNapiValue(const Napi::Env &env, const Napi::Value &val) {
        // Assuming val is an object, you may test if it actually is
        Napi::Object obj = val.ToObject();

        // Set all values
        custom_class c;
        c.s1 = obj.Get("s1").ToString();
        c.s2 = obj.Get("s2").ToString();
        c.i = obj.Get("i").ToNumber();

        // napi_tools also has functions to convert from napi values:
        c.ints = napi_tools::util::conversions::convertToCpp<std::vector<int>>(obj.Get("ints"));
        return c;
    }
};

Other tools

Catch all exceptions

To catch all exceptions possibly thrown by c++ to throw them in the javascript process, use the TRY/CATCH_EXCEPTIONS macros:

void someFunction(const Napi::CallbackInfo &info) {
    TRY
        // Some code that may throw an exception
    CATCH_EXCEPTIONS
}

Check argument types

To check if the number of supplied arguments, and the argument types match, use the CHECK_ARGS(...) macro. A Napi::Error will be thrown if the arguments do not match. Supported type names are: string, number, function, object, boolean, array, buffer, undefined, null.

Example:

void someFunction(const Napi::CallbackInfo &info) {
    using namespace napi_tools;
    // Expect a function, a string, a number and a boolean
    CHECK_ARGS(function, string, number, boolean);
}

It is also possible to allow multiple types as an input:

void someFunction(const Napi::CallbackInfo &info) {
    using namespace napi_tools;
    // Expect a string, number, null or undefined
    CHECK_ARGS(string | number | null | undefined);
}

Export functions

To make exporting functions in your ìnit method easier, n-api-tools provides the EXPORT_FUNCTION(exports, env, fn) macro. This macro will "export" functions using their C++ function name to javascript.

Example:

Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
    EXPORT_FUNCTION(exports, env, func1);
    EXPORT_FUNCTION(exports, env, func2);
    EXPORT_FUNCTION(exports, env, anotherFunc);

    return exports;
}

// Initialize the module
NODE_API_MODULE(some_module, InitAll)
1.2.0

2 years ago

1.1.1

2 years ago

1.1.0

3 years ago

1.0.9

3 years ago

1.0.8

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago