Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,2 @@
*node_modules
package-lock.json

View File

@@ -0,0 +1,120 @@
find_program(
NODE
node
REQUIRED
)
execute_process(
COMMAND ${NODE} --version
OUTPUT_VARIABLE NODE_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# We need Node-API version 6 or higher
set(NODE_API_VERSION_REQUIRED 6)
include(NodeApiVersion)
require_node_api_version("${NODE_VERSION}" "${NODE_API_VERSION_REQUIRED}")
find_program(
NPM
npm
REQUIRED
)
# Include Node-API wrappers
# FIXME (aw): we want this as an requirement, not implicitely install it by ourself
execute_process(
COMMAND
${NPM} list -p node-addon-api --loglevel=error | grep node-addon-api
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
OUTPUT_VARIABLE
NODE_ADDON_API_PACKAGE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(NODE_ADDON_API_INSTALL_VERSION "8.1")
message(STATUS "Adding node-addon-api@${NODE_ADDON_API_INSTALL_VERSION}")
if (NOT NODE_ADDON_API_PACKAGE_DIR)
execute_process(
COMMAND
${NPM} install node-addon-api@${NODE_ADDON_API_INSTALL_VERSION}
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
OUTPUT_QUIET
RESULT_VARIABLE
NPM_INSTALL_NODE_ADDON_API_FAILED
)
if (NPM_INSTALL_NODE_ADDON_API_FAILED)
message(FATAL_ERROR "Installation of node-addon-api failed")
endif ()
endif ()
execute_process(
COMMAND ${NODE} -p "require('node-addon-api').include"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
OUTPUT_VARIABLE NODE_ADDON_API_DIR
)
string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR "${NODE_ADDON_API_DIR}")
find_path(NODEJS_INCLUDE_DIR "node_api.h"
PATH_SUFFIXES
"node"
"node16"
"node17"
"node18"
"node19"
"node20"
"node21"
"node22"
"nodejs/src"
HINTS
"$ENV{HOME}/.nvm/versions/node/${NODE_VERSION}/include"
REQUIRED
)
add_library(everestjs SHARED)
target_sources(everestjs
PRIVATE
everestjs.cpp
conversions.cpp
js_exec_ctx.cpp
)
target_compile_options(everestjs PRIVATE ${COMPILER_WARNING_OPTIONS})
# define NAPI_VERSION
target_compile_definitions(everestjs
PRIVATE
-DNAPI_VERSION=${NODE_API_VERSION_REQUIRED}
-DNAPI_CPP_EXCEPTIONS
)
set_target_properties(everestjs PROPERTIES PREFIX "" SUFFIX ".node")
target_include_directories(everestjs
PRIVATE
$<BUILD_INTERFACE:${NODE_ADDON_API_DIR}>
$<BUILD_INTERFACE:${NODEJS_INCLUDE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
target_link_libraries(everestjs
PRIVATE
everest::framework
everest::log
)
install(
TARGETS everestjs
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}/everest/node_modules/everestjs
)
install(
FILES
index.js
package.json
DESTINATION ${CMAKE_INSTALL_LIBDIR}/everest/node_modules/everestjs
)

View File

@@ -0,0 +1,213 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#include "conversions.hpp"
#include <everest/exceptions.hpp>
#include <everest/logging.hpp>
#include <utils/error/error_json.hpp>
namespace EverestJs {
Everest::json convertToJson(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsNull() || value.IsUndefined()) {
return Everest::json(nullptr);
} else if (value.IsString()) {
return Everest::json(std::string(value.As<Napi::String>()));
} else if (value.IsNumber()) {
int64_t intNumber = value.As<Napi::Number>();
double floatNumber = value.As<Napi::Number>();
if (floatNumber == intNumber)
return Everest::json(intNumber);
return Everest::json(floatNumber);
} else if (value.IsBoolean()) {
return Everest::json(bool(value.As<Napi::Boolean>()));
} else if (value.IsArray()) {
auto j = Everest::json::array();
Napi::Array array = value.As<Napi::Array>();
for (uint64_t i = 0; i < array.Length(); i++) {
Napi::Value entry = Napi::Value(array[i]);
j[i] = convertToJson(entry);
}
return j;
} else if (value.IsObject() && value.Type() == napi_object) {
auto j = Everest::json({});
Napi::Object obj = value.As<Napi::Object>();
Napi::Array keys = obj.GetPropertyNames();
for (uint64_t i = 0; i < keys.Length(); i++) {
Napi::Value key = keys[i];
if (key.IsString()) {
std::string k = key.As<Napi::String>();
Napi::Value v = Napi::Value(obj[k]);
j[k] = convertToJson(v);
} else {
EVTHROW(EVEXCEPTION(Everest::EverestApiError,
"Javascript type of object key can not be converted to Everest::json: ",
napi_valuetype_strings[key.Type()]));
}
}
return j;
}
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Javascript type can not be converted to Everest::json: ",
napi_valuetype_strings[value.Type()]));
}
Everest::TelemetryMap convertToTelemetryMap(const Napi::Object& obj) {
BOOST_LOG_FUNCTION();
Everest::TelemetryMap telemetry;
Napi::Array keys = obj.GetPropertyNames();
for (uint64_t i = 0; i < keys.Length(); i++) {
Napi::Value key = keys[i];
if (key.IsString()) {
std::string k = key.As<Napi::String>();
Napi::Value value = Napi::Value(obj[k]);
if (value.IsString()) {
telemetry[k] = std::string(value.As<Napi::String>());
} else if (value.IsNumber()) {
int intNumber = value.As<Napi::Number>();
double floatNumber = value.As<Napi::Number>();
if (floatNumber == intNumber) {
telemetry[k] = intNumber;
} else {
telemetry[k] = floatNumber;
}
} else if (value.IsBoolean()) {
telemetry[k] = bool(value.As<Napi::Boolean>());
}
}
}
return telemetry;
}
Napi::Value convertToNapiValue(const Napi::Env& env, const json& value) {
BOOST_LOG_FUNCTION();
if (value.is_null()) {
return env.Null();
} else if (value.is_string()) {
return Napi::String::New(env, std::string(value));
} else if (value.is_number_integer()) {
return Napi::Number::New(env, int64_t(value));
} else if (value.is_number_float()) {
return Napi::Number::New(env, double(value));
} else if (value.is_boolean()) {
return Napi::Boolean::New(env, bool(value));
} else if (value.is_array()) {
Napi::Array v = Napi::Array::New(env);
for (uint64_t i = 0; i < value.size(); i++) {
v.Set(i, convertToNapiValue(env, value[i]));
}
return v;
} else if (value.is_object()) {
Napi::Object v = Napi::Object::New(env);
for (auto& el : value.items()) {
v.Set(el.key(), convertToNapiValue(env, el.value()));
}
return v;
}
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Javascript type can not be converted to Napi::Value: ", value));
}
Everest::error::Error convertToError(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
Everest::json j = convertToJson(value);
return j.get<Everest::error::Error>();
}
Everest::error::ErrorType convertToErrorType(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsString()) {
return Everest::error::ErrorType(std::string(value.As<Napi::String>()));
}
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Javascript type can not be converted to Everest::error::ErrorType: ",
napi_valuetype_strings[value.Type()]));
}
Everest::error::ErrorSubType convertToErrorSubType(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsString()) {
return Everest::error::ErrorSubType(std::string(value.As<Napi::String>()));
}
EVTHROW(EVEXCEPTION(Everest::EverestApiError,
"Javascript type can not be converted to Everest::error::ErrorSubType: ",
napi_valuetype_strings[value.Type()]));
}
Everest::error::Severity convertToErrorSeverity(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsString()) {
return Everest::error::string_to_severity(std::string(value.As<Napi::String>()));
}
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Javascript type can not be converted to Everest::error::Severity: ",
napi_valuetype_strings[value.Type()]));
}
Everest::error::State convertToErrorState(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsString()) {
return Everest::error::string_to_state(std::string(value.As<Napi::String>()));
}
EVTHROW(EVEXCEPTION(Everest::EverestApiError, "Javascript type can not be converted to Everest::error::State: ",
napi_valuetype_strings[value.Type()]));
}
Napi::Value convertToNapiValue(const Napi::Env& env, const Everest::error::Error& error) {
BOOST_LOG_FUNCTION();
json j(error);
Napi::Value res = convertToNapiValue(env, j);
return res;
}
bool isSingleErrorStateCondition(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsArray()) {
return false;
}
return true;
}
Everest::error::ErrorStateMonitor::StateCondition convertToErrorStateCondition(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsObject()) {
Napi::Object obj = value.As<Napi::Object>();
Napi::Value type = obj.Get("type");
Napi::Value sub_type = obj.Get("sub_type");
Napi::Value active = obj.Get("active");
return Everest::error::ErrorStateMonitor::StateCondition(
convertToErrorType(type), convertToErrorSubType(sub_type), bool(active.As<Napi::Boolean>()));
}
EVTHROW(EVEXCEPTION(Everest::EverestApiError,
"Javascript type can not be converted to Everest::error::ErrorStateMonitor::StateCondition: ",
napi_valuetype_strings[value.Type()]));
}
std::list<Everest::error::ErrorStateMonitor::StateCondition>
convertToErrorStateConditionList(const Napi::Value& value) {
BOOST_LOG_FUNCTION();
if (value.IsArray()) {
std::list<Everest::error::ErrorStateMonitor::StateCondition> conditions;
Napi::Array array = value.As<Napi::Array>();
for (uint64_t i = 0; i < array.Length(); i++) {
Napi::Value entry = Napi::Value(array[i]);
conditions.push_back(convertToErrorStateCondition(entry));
}
return conditions;
}
EVTHROW(EVEXCEPTION(
Everest::EverestApiError,
"Javascript type can not be converted to std::list<Everest::error::ErrorStateMonitor::StateCondition>: ",
napi_valuetype_strings[value.Type()]));
}
} // namespace EverestJs

View File

@@ -0,0 +1,50 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#ifndef CONVERSIONS_HPP
#define CONVERSIONS_HPP
#include <framework/everest.hpp>
#include <napi.h>
#include <utils/conversions.hpp>
#include <utils/types.hpp>
#include <utils/error.hpp>
#include <utils/error/error_state_monitor.hpp>
namespace EverestJs {
static const char* const napi_valuetype_strings[] = {
"undefined", //
"null", //
"boolean", //
"number", //
"string", //
"symbol", //
"object", //
"function", //
"external", //
"bigint", //
};
Everest::json convertToJson(const Napi::Value& value);
Everest::json convertToConfigMap(const Everest::json& json_config);
Everest::TelemetryMap convertToTelemetryMap(const Napi::Object& obj);
Napi::Value convertToNapiValue(const Napi::Env& env, const Everest::json& value);
// Error related
Everest::error::Error convertToError(const Napi::Value& value);
Everest::error::ErrorType convertToErrorType(const Napi::Value& value);
Everest::error::ErrorSubType convertToErrorSubType(const Napi::Value& value);
Everest::error::Severity convertToErrorSeverity(const Napi::Value& value);
Everest::error::State convertToErrorState(const Napi::Value& value);
Napi::Value convertToNapiValue(const Napi::Env& env, const Everest::error::Error& error);
// ErrorStateCondition related
bool isSingleErrorStateCondition(const Napi::Value& value);
Everest::error::ErrorStateMonitor::StateCondition convertToErrorStateCondition(const Napi::Value& value);
std::list<Everest::error::ErrorStateMonitor::StateCondition> convertToErrorStateConditionList(const Napi::Value& value);
} // namespace EverestJs
#endif // CONVERSIONS_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
const util = require('util');
const addon = require('./everestjs.node');
const helpers = {
get_default: (obj, key, defaultValue) => ((obj[key] === undefined) ? defaultValue : obj[key]),
to_string: (...values) => {
let log_string = '';
values.forEach((value) => {
if (typeof value == 'string') {
log_string += value;
} else if (typeof value == 'number') {
log_string += value;
} else if (typeof value == 'boolean') {
log_string += value ? 'true' : 'false';
} else if (typeof value == 'object') {
log_string += util.inspect(value, false, 6, true);
} else {
throw new Error(`The logging function cannot handle input of type ${typeof value}`);
}
});
return log_string;
},
};
const EverestModule = function EverestModule(handler_setup, user_settings) {
const env_settings = {
module: process.env.EV_MODULE,
prefix: process.env.EV_PREFIX,
logging_config_file: process.env.EV_LOG_CONF_FILE,
mqtt_everest_prefix: process.env.EV_MQTT_EVEREST_PREFIX,
mqtt_external_prefix: process.env.EV_MQTT_EXTERNAL_PREFIX,
mqtt_broker_socket_path: process.env.EV_MQTT_BROKER_SOCKET_PATH,
mqtt_server_address: process.env.EV_MQTT_BROKER_HOST,
mqtt_server_port: process.env.EV_MQTT_BROKER_PORT,
validate_schema: process.env.EV_VALIDATE_SCHEMA,
};
const settings = { ...env_settings, ...user_settings };
if (!settings.module) {
throw new Error('parameter "module" is missing');
}
const config = {
module: settings.module,
prefix: settings.prefix,
logging_config_file: settings.logging_config_file,
mqtt_everest_prefix: settings.mqtt_everest_prefix,
mqtt_external_prefix: helpers.get_default(settings, 'mqtt_external_prefix', ''),
mqtt_broker_socket_path: helpers.get_default(settings, 'mqtt_broker_socket_path', ''),
mqtt_server_address: helpers.get_default(settings, 'mqtt_server_address', ''),
mqtt_server_port: helpers.get_default(settings, 'mqtt_server_port', 0),
validate_schema: helpers.get_default(settings, 'validate_schema', false),
};
function callbackWrapper(on, request, ...args) {
const result = request(...args);
Promise.resolve(result).then(on.fulfill, on.reject);
}
const available_handlers = addon.boot_module.call(this, config, callbackWrapper);
const module_setup = {
setup: available_handlers,
info: this.info,
config: this.config,
mqtt: this.mqtt,
telemetry: this.telemetry,
};
if (this.mqtt === undefined) {
const missing_mqtt_getter = {
get() { throw new Error('External mqtt not available - missing enable_external_mqtt in manifest?'); },
};
Object.defineProperty(module_setup, 'mqtt', missing_mqtt_getter);
Object.defineProperty(this, 'mqtt', missing_mqtt_getter);
}
if (this.telemetry === undefined) {
const missing_telemetry_getter = {
get() { throw new Error('Telemetry not available - missing enable_telemetry in manifest?'); },
};
Object.defineProperty(module_setup, 'telemetry', missing_telemetry_getter);
Object.defineProperty(this, 'telemetry', missing_telemetry_getter);
}
// check, if we need to register cmds
if (typeof handler_setup === 'undefined') {
if (Object.keys(available_handlers.provides).length !== 0) {
throw new Error('handler setup callback is missing - you need to register at least one command');
}
return addon.signal_ready.call(this);
}
if (typeof handler_setup === 'function') {
return Promise.resolve(handler_setup.call({}, module_setup)).then(
() => addon.signal_ready.call(this)
);
}
throw new Error('handler setup callback needs to be of type function');
};
// setup log handlers
exports.evlog = {};
Object.keys(addon.log).forEach((key) => {
exports.evlog[key] = (...log_args) => addon.log[key](helpers.to_string(...log_args));
});
let boot_module_called = false;
exports.boot_module = (handler_setup, settings) => {
if (boot_module_called) throw Error('Calling initModule more than once is not supported right now');
boot_module_called = true;
return new EverestModule(handler_setup, settings);
};

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#include "js_exec_ctx.hpp"
#include "utils.hpp"
void JsExecCtx::tramp(Napi::Env env, Napi::Function callback, std::nullptr_t* context, JsExecCtx* this_) {
(void)context; // this is unused in this callback
try {
std::vector<napi_value> args{this_->result_handler_ref.Value()};
if (this_->arg_func != nullptr) {
// append args from our arg function via move magic ...
auto append_args = this_->arg_func(env);
args.reserve(args.size() + append_args.size());
std::move(std::begin(append_args), std::end(append_args), std::back_inserter(args));
append_args.clear();
}
callback.Call(args);
} catch (std::exception& e) {
EVLOG_AND_RETHROW(env);
}
}
Napi::Value JsExecCtx::on_fulfill(const Napi::CallbackInfo& info) {
JsExecCtx* this_ = reinterpret_cast<JsExecCtx*>(info.Data());
if (this_->res_func != nullptr) {
this_->res_func(info, false);
}
this_->promise.set_value();
return info.Env().Undefined();
}
Napi::Value JsExecCtx::on_reject(const Napi::CallbackInfo& info) {
JsExecCtx* this_ = reinterpret_cast<JsExecCtx*>(info.Data());
if (this_->res_func != nullptr) {
this_->res_func(info, true);
} else {
// there is no catch handler registered, so we throw
throw Napi::Error::New(
info.Env(),
"JsExecCtx call into javascript code got rejected and could not be handled (rejection handler not defined");
}
this_->promise.set_value();
return info.Env().Undefined();
}
void JsExecCtx::exec(const ArgFuncType& arg_func, const ResFuncType& res_func) {
// FIXME (aw): we're blocking all other threads trying to call this function
// a proper solution should be found
std::unique_lock<std::mutex> lock(exec_mutex);
this->arg_func = arg_func;
this->res_func = res_func;
promise = std::promise<void>();
tsfn.BlockingCall(this);
promise.get_future().get();
}

View File

@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
//
// author: aw@pionix.de
//
#ifndef JS_EXEC_CTX_HPP
#define JS_EXEC_CTX_HPP
#include <future>
#include <napi.h>
class JsExecCtx {
private:
static void tramp(Napi::Env env, Napi::Function callback, std::nullptr_t*, JsExecCtx* this_);
static Napi::Value on_fulfill(const Napi::CallbackInfo& info);
static Napi::Value on_reject(const Napi::CallbackInfo& info);
public:
using TsfnType = Napi::TypedThreadSafeFunction<std::nullptr_t, JsExecCtx, JsExecCtx::tramp>;
using ArgFuncType = std::function<std::vector<napi_value>(Napi::Env&)>;
using ResFuncType = std::function<void(const Napi::CallbackInfo&, bool)>;
// FIXME (aw): proper module_instance handling if nullptr
JsExecCtx(const Napi::Env& env, const Napi::Function& func, const std::string& res_name = "RequestDispatcher") :
tsfn(TsfnType::New(env, func, res_name, 0, 1)),
result_handler_ref(Napi::Persistent(Napi::Object::New(env))),
func_ref(Napi::Persistent(func)) {
result_handler_ref.Value().DefineProperty(Napi::PropertyDescriptor::Value(
"fulfill", Napi::Function::New(env, on_fulfill, nullptr, this), napi_enumerable));
result_handler_ref.Value().DefineProperty(Napi::PropertyDescriptor::Value(
"reject", Napi::Function::New(env, on_reject, nullptr, this), napi_enumerable));
}
~JsExecCtx() {
tsfn.Release();
}
void exec(const ArgFuncType& arg_func, const ResFuncType& res_func);
private:
ArgFuncType arg_func;
ResFuncType res_func;
// FIXME (aw): will the referenced object be GC'd when the references get destroyed?
// and is okay to be destroyed in our async thread?
TsfnType tsfn;
Napi::ObjectReference result_handler_ref;
Napi::FunctionReference func_ref{};
std::mutex exec_mutex;
std::promise<void> promise;
};
#endif // JS_EXEC_CTX_HPP

View File

@@ -0,0 +1,9 @@
{
"name": "everestjs",
"main": "index.js",
"version": "0.25.0",
"description": "EVerest API for node.js",
"dependencies": {
"node-addon-api": "^3.2.1"
}
}

View File

@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#ifndef UTILS_HPP
#define UTILS_HPP
#include <everest/logging.hpp>
#include <everest/metamacros.hpp>
namespace EverestJs {
// this is needed to get javascript stacktraces whenever possible while still logging boost error information
#define EVLOG_AND_RETHROW(...) \
do { \
try { \
throw; \
} catch (Napi::Error & e) { \
try { \
BOOST_THROW_EXCEPTION(boost::enable_error_info(e) \
<< boost::log::BOOST_LOG_VERSION_NAMESPACE::current_scope()); \
} catch (boost::exception & ex) { \
char const* const* f = boost::get_error_info<boost::throw_file>(ex); \
int const* l = boost::get_error_info<boost::throw_line>(ex); \
char const* const* fn = boost::get_error_info<boost::throw_function>(ex); \
EVLOG_critical << "Catched top level Napi::Error, forwarding to javascript..." << std::endl \
<< (f ? *f : "") << ":" << (l ? std::to_string(*l) : "") << " in Function " \
<< (fn ? *fn : "") << std::endl \
<< boost::diagnostic_information(e, true) << boost::diagnostic_information(ex, false) \
<< std::endl \
<< "==============================" << std::endl \
<< std::endl; \
} \
throw; /* this will forward the exception back to javascript and enable js backtraces */ \
} catch (std::exception & e) { \
try { \
BOOST_THROW_EXCEPTION(boost::enable_error_info(e) \
<< boost::log::BOOST_LOG_VERSION_NAMESPACE::current_scope()); \
} catch (boost::exception & ex) { \
char const* const* f = boost::get_error_info<boost::throw_file>(ex); \
int const* l = boost::get_error_info<boost::throw_line>(ex); \
char const* const* fn = boost::get_error_info<boost::throw_function>(ex); \
EVLOG_critical << "Catched top level exception, forwarding to javascript..." << std::endl \
<< (f ? *f : "") << ":" << (l ? std::to_string(*l) : "") << " in Function " \
<< (fn ? *fn : "") << std::endl \
<< boost::diagnostic_information(e, true) << boost::diagnostic_information(ex, false) \
<< std::endl \
<< "==============================" << std::endl \
<< std::endl; \
/* this will forward the exception to javascript and enable js backtraces */ \
metamacro_if_eq(0, metamacro_argcount(__VA_ARGS__))(throw;)(EVTHROW(Napi::Error::New( \
metamacro_at(0, __VA_ARGS__), Napi::String::New(metamacro_at(0, __VA_ARGS__), e.what())));) \
} \
} \
} while (0)
} // namespace EverestJs
#endif // UTILS_HPP