diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d849c50 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.node \ No newline at end of file diff --git a/LICENSE b/LICENSE index defa856..5e8a03e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2022 Akshay Ganeshen +Copyright (c) 2024 Matheus Sampaio Queiroga Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2a73726..7f9f25a 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,3 @@ # napi-go A Go library for building Node.js Native Addons using Node-API. - -## Usage - -Use `go get` to install the library: - -```sh -go get -u github.com/akshayganeshen/napi-go -``` - -Then use the library to define handlers: - -```go -package handlers - -import "github.com/akshayganeshen/napi-go" - -func MyHandler(env napi.Env, info napi.CallbackInfo) napi.Value { - return nil -} -``` - -Next, create a `main.go` that registers all module exports: - -```go -package main - -import "github.com/akshayganeshen/napi-go/entry" - -func init() { - entry.Export("myHandler", MyHandler) -} - -func main() {} -``` - -Finally, build the Node.js addon using `go build`: - -```sh -go build -buildmode=c-shared -o "example.node" . -``` - -The output `.node` file can now be imported via `require`: - -```js -const example = require("./example.node"); - -example.myHandler(); -``` - -### JS Helpers - -In addition to the Node-API exposed via package `napi`, the `napi-go/js` -package provides functions similar to the `syscall/js` standard library. - -```go -package main - -import ( - "github.com/akshayganeshen/napi-go/entry" - "github.com/akshayganeshen/napi-go/js" -) - -func init() { - entry.Export("myCallback", js.AsCallback(MyCallback)) -} - -func MyCallback(env js.Env, this js.Value, args []js.Value) any { - return map[string]any{ - "message": "hello world", - "args": args, - } -} - -func main() {} -``` - -## Examples - -Check out the example addons in [`docs/examples`](docs/examples). diff --git a/array.go b/array.go new file mode 100644 index 0000000..0595608 --- /dev/null +++ b/array.go @@ -0,0 +1,85 @@ +package napi + +import ( + "iter" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type Array struct{ value } + +// Convert [ValueType] to [*Array]. +func ToArray(o ValueType) *Array { return &Array{o} } + +// Create Array. +func CreateArray(env EnvType, size ...int) (*Array, error) { + sizeOf := 0 + if len(size) > 0 { + sizeOf = size[0] + } + napiValue, err := napi.Value(nil), error(nil) + if sizeOf == 0 { + napiValue, err = mustValueErr(napi.CreateArray(env.NapiValue())) + } else { + napiValue, err = mustValueErr(napi.CreateArrayWithLength(env.NapiValue(), sizeOf)) + } + // Check error exists + if err != nil { + return nil, err + } + return ToArray(N_APIValue(env, napiValue)), nil +} + +// Get array length. +func (arr *Array) Length() (int, error) { + return mustValueErr(napi.GetArrayLength(arr.NapiEnv(), arr.NapiValue())) +} + +// Delete index elemente from array. +func (arr *Array) Delete(index int) (bool, error) { + return mustValueErr(napi.DeleteElement(arr.NapiEnv(), arr.NapiValue(), index)) +} + +// Set value in index +func (arr *Array) Set(index int, value ValueType) error { + return singleMustValueErr(napi.SetElement(arr.NapiEnv(), arr.NapiValue(), index, value.NapiValue())) +} + +// Get Value from index +func (arr *Array) Get(index int) (ValueType, error) { + napiValue, err := mustValueErr(napi.GetElement(arr.NapiEnv(), arr.NapiValue(), index)) + if err != nil { + return nil, err + } + return N_APIValue(arr.Env(), napiValue), nil +} + +// Get values with [iter.Seq] +func (arr *Array) Seq() iter.Seq[ValueType] { + length, err := arr.Length() + if err != nil { + return nil + } + return func(yield func(ValueType) bool) { + for index := range length { + if value, err := arr.Get(index); err == nil { + if !yield(value) { + return + } + } + } + } +} + +func (arr *Array) Append(values ...ValueType) error { + length, err := arr.Length() + if err != nil { + return err + } + for valueIndex := range values { + if err = arr.Set(length+valueIndex, values[valueIndex]); err != nil { + return err + } + } + return nil +} diff --git a/boolean.go b/boolean.go new file mode 100644 index 0000000..8f6a155 --- /dev/null +++ b/boolean.go @@ -0,0 +1,20 @@ +package napi + +import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" + +type Boolean struct{ value } + +// Convert [ValueType] to [*Boolean] +func ToBoolean(o ValueType) *Boolean { return &Boolean{o} } + +func CreateBoolean(env EnvType, value bool) (*Boolean, error) { + v, err := mustValueErr(napi.GetBoolean(env.NapiValue(), value)) + if err != nil { + return nil, err + } + return ToBoolean(N_APIValue(env, v)), nil +} + +func (bo *Boolean) Value() (bool, error) { + return mustValueErr(napi.GetValueBool(bo.NapiEnv(), bo.NapiValue())) +} diff --git a/buffer.go b/buffer.go new file mode 100644 index 0000000..7027d5f --- /dev/null +++ b/buffer.go @@ -0,0 +1,36 @@ +package napi + +import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" + +type Buffer struct{ value } + +// Convert [ValueType] to [*Buffer]. +func ToBuffer(o ValueType) *Buffer { return &Buffer{o} } + +// Create new Buffer with length +func CreateBuffer(env EnvType, length int) (*Buffer, error) { + napiValue, err := mustValueErr(napi.CreateBuffer(env.NapiValue(), length)) + if err != nil { + return nil, err + } + return ToBuffer(N_APIValue(env, napiValue)), nil +} + +// Copy []byte to Node::Buffer struct +func CopyBuffer(env EnvType, buff []byte) (*Buffer, error) { + napiValue, err := mustValueErr(napi.CreateBufferCopy(env.NapiValue(), buff)) + if err != nil { + return nil, err + } + return ToBuffer(N_APIValue(env, napiValue)), nil +} + +// Get size of buffer +func (buff *Buffer) Length() (int, error) { + return mustValueErr(napi.GetBufferInfoSize(buff.NapiEnv(), buff.NapiValue())) +} + +// return []byte from Buffer value +func (buff *Buffer) Data() ([]byte, error) { + return mustValueErr(napi.GetBufferInfoData(buff.NapiEnv(), buff.NapiValue())) +} diff --git a/c++/napi-inl.deprecated.h b/c++/napi-inl.deprecated.h new file mode 100644 index 0000000..dfb7dea --- /dev/null +++ b/c++/napi-inl.deprecated.h @@ -0,0 +1,186 @@ +#ifndef SRC_NAPI_INL_DEPRECATED_H_ +#define SRC_NAPI_INL_DEPRECATED_H_ + +//////////////////////////////////////////////////////////////////////////////// +// PropertyDescriptor class +//////////////////////////////////////////////////////////////////////////////// + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + napi_value name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, nullptr}); + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Name name, Getter getter, napi_property_attributes attributes, void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor(nameValue, getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::AccessorCallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, setter, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { + using CbData = details::AccessorCallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({getter, setter, nullptr}); + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Accessor( + nameValue, getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({cb, nullptr}); + + return PropertyDescriptor({utf8name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + napi_value name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + // TODO: Delete when the function is destroyed + auto callbackData = new CbData({cb, nullptr}); + + return PropertyDescriptor({nullptr, + name, + CbData::Wrapper, + nullptr, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Name name, Callable cb, napi_property_attributes attributes, void* data) { + napi_value nameValue = name; + return PropertyDescriptor::Function(nameValue, cb, attributes, data); +} + +#endif // !SRC_NAPI_INL_DEPRECATED_H_ \ No newline at end of file diff --git a/c++/napi-inl.h b/c++/napi-inl.h new file mode 100644 index 0000000..1f35c96 --- /dev/null +++ b/c++/napi-inl.h @@ -0,0 +1,6936 @@ +#ifndef SRC_NAPI_INL_H_ +#define SRC_NAPI_INL_H_ + +//////////////////////////////////////////////////////////////////////////////// +// Node-API C++ Wrapper Classes +// +// Inline header-only implementations for "Node-API" ABI-stable C APIs for +// Node.js. +//////////////////////////////////////////////////////////////////////////////// + +// Note: Do not include this file directly! Include "napi.h" instead. +// This should be a no-op and is intended for better IDE integration. +#include "napi.h" + +#include +#include +#include +#if NAPI_HAS_THREADS +#include +#endif // NAPI_HAS_THREADS +#include +#include + +namespace Napi { + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Helpers to handle functions exposed from C++ and internal constants. +namespace details { + +// New napi_status constants not yet available in all supported versions of +// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h. +constexpr int napi_no_external_buffers_allowed = 22; + +template +inline void default_basic_finalizer(node_addon_api_basic_env /*env*/, + void* data, + void* /*hint*/) { + delete static_cast(data); +} + +// Attach a data item to an object and delete it when the object gets +// garbage-collected. +// TODO: Replace this code with `napi_add_finalizer()` whenever it becomes +// available on all supported versions of Node.js. +template < + typename FreeType, + node_addon_api_basic_finalize finalizer = default_basic_finalizer> +inline napi_status AttachData(napi_env env, + napi_value obj, + FreeType* data, + void* hint = nullptr) { + napi_status status; +#if (NAPI_VERSION < 5) + napi_value symbol, external; + status = napi_create_symbol(env, nullptr, &symbol); + if (status == napi_ok) { + status = napi_create_external(env, data, finalizer, hint, &external); + if (status == napi_ok) { + napi_property_descriptor desc = {nullptr, + symbol, + nullptr, + nullptr, + nullptr, + external, + napi_default, + nullptr}; + status = napi_define_properties(env, obj, 1, &desc); + } + } +#else // NAPI_VERSION >= 5 + status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr); +#endif + return status; +} + +// For use in JS to C++ callback wrappers to catch any Napi::Error exceptions +// and rethrow them as JavaScript exceptions before returning from the callback. +template +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline napi_value WrapCallback(napi_env env, Callable callback) { +#else +inline napi_value WrapCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + return callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + return nullptr; + } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + return nullptr; + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + return nullptr; + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else // NODE_ADDON_API_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + return callback(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + +// For use in JS to C++ void callback wrappers to catch any Napi::Error +// exceptions and rethrow them as JavaScript exceptions before returning from +// the callback. +template +inline void WrapVoidCallback(Callable callback) { +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#else // NAPI_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + callback(); +#endif // NAPI_CPP_EXCEPTIONS +} + +// For use in JS to C++ void callback wrappers to catch _any_ thrown exception +// and rethrow them as JavaScript exceptions before returning from the callback, +// wrapping in an Napi::Error as needed. +template +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline void WrapVoidCallback(napi_env env, Callable callback) { +#else +inline void WrapVoidCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else + // When C++ exceptions are disabled, there is no need to catch and rethrow C++ + // exceptions. JS errors should be thrown with + // `Error::ThrowAsJavaScriptException`. + callback(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + +template +struct CallbackData { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + CallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->callback(callbackInfo); + }); + } + + Callable callback; + void* data; +}; + +template +struct CallbackData { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + CallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->callback(callbackInfo); + return nullptr; + }); + } + + Callable callback; + void* data; +}; + +template +napi_value TemplatedVoidCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + Callback(cbInfo); + return nullptr; + }); +} + +template +napi_value TemplatedCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + // MSVC requires to copy 'Callback' function pointer to a local variable + // before invoking it. + auto callback = Callback; + return callback(cbInfo); + }); +} + +template +napi_value TemplatedInstanceCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + return instance ? (instance->*UnwrapCallback)(cbInfo) : Napi::Value(); + }); +} + +template +napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) + NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + if (instance) (instance->*UnwrapCallback)(cbInfo); + return nullptr; + }); +} + +template +struct FinalizeData { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void Wrapper(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data)); + delete finalizeData; + }); + } + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void Wrapper(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGC, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void WrapperWithHint(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); + delete finalizeData; + }); + } + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void WrapperWithHint(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGCWithHint, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + + static inline void WrapperGCWithoutData(napi_env env, + void* /*data*/, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env); + delete finalizeData; + }); + } + + static inline void WrapperGC(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data)); + delete finalizeData; + }); + } + + static inline void WrapperGCWithHint(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); + delete finalizeData; + }); + } + + Finalizer callback; + Hint* hint; +}; + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +template , + typename FinalizerDataType = void> +struct ThreadSafeFinalize { + static inline void Wrapper(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env)); + delete finalizeData; + } + + static inline void FinalizeWrapperWithData(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), finalizeData->data); + delete finalizeData; + } + + static inline void FinalizeWrapperWithContext(napi_env env, + void* rawFinalizeData, + void* rawContext) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), static_cast(rawContext)); + delete finalizeData; + } + + static inline void FinalizeFinalizeWrapperWithDataAndContext( + napi_env env, void* rawFinalizeData, void* rawContext) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback( + Env(env), finalizeData->data, static_cast(rawContext)); + delete finalizeData; + } + + FinalizerDataType* data; + Finalizer callback; +}; + +template +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, napi_value jsCallback, void* context, void* data) { + details::WrapVoidCallback(env, [&]() { + call(env, + Function(env, jsCallback), + static_cast(context), + static_cast(data)); + }); +} + +template +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, + napi_value jsCallback, + void* /*context*/, + void* /*data*/) { + details::WrapVoidCallback(env, [&]() { + if (jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } + }); +} + +#if NAPI_VERSION > 4 + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, std::nullptr_t /*cb*/) { + return nullptr; +} + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, Napi::Function cb) { + return cb; +} + +#else +template +napi_value DefaultCallbackWrapper(napi_env env, Napi::Function cb) { + if (cb.IsEmpty()) { + return TSFN::EmptyFunctionFactory(env); + } + return cb; +} +#endif // NAPI_VERSION > 4 +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +template +struct AccessorCallbackData { + static inline napi_value GetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->getterCallback(callbackInfo); + }); + } + + static inline napi_value SetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->setterCallback(callbackInfo); + return nullptr; + }); + } + + Getter getterCallback; + Setter setterCallback; + void* data; +}; + +// Debugging-purpose C++-style variant of sprintf(). +inline std::string StringFormat(const char* format, ...) { + std::string result; + va_list args; + va_start(args, format); + int len = vsnprintf(nullptr, 0, format, args); + result.resize(len); + vsnprintf(&result[0], len + 1, format, args); + va_end(args); + return result; +} + +template +class HasExtendedFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + +template +class HasBasicFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + +} // namespace details + +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED +#include "napi-inl.deprecated.h" +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + +//////////////////////////////////////////////////////////////////////////////// +// Module registration +//////////////////////////////////////////////////////////////////////////////// + +// Register an add-on based on an initializer function. +#define NODE_API_MODULE(modname, regfunc) \ + static napi_value __napi_##regfunc(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, regfunc); \ + } \ + NAPI_MODULE(modname, __napi_##regfunc) + +// Register an add-on based on a subclass of `Addon` with a custom Node.js +// module name. +#define NODE_API_NAMED_ADDON(modname, classname) \ + static napi_value __napi_##classname(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, &classname::Init); \ + } \ + NAPI_MODULE(modname, __napi_##classname) + +// Register an add-on based on a subclass of `Addon` with the Node.js module +// name given by node-gyp from the `target_name` in binding.gyp. +#define NODE_API_ADDON(classname) \ + NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname) + +// Adapt the NAPI_MODULE registration function: +// - Wrap the arguments in NAPI wrappers. +// - Catch any NAPI errors and rethrow as JS exceptions. +inline napi_value RegisterModule(napi_env env, + napi_value exports, + ModuleRegisterCallback registerCallback) { + return details::WrapCallback(env, [&] { + return napi_value( + registerCallback(Napi::Env(env), Napi::Object(env, exports))); + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Maybe class +//////////////////////////////////////////////////////////////////////////////// + +template +bool Maybe::IsNothing() const { + return !_has_value; +} + +template +bool Maybe::IsJust() const { + return _has_value; +} + +template +void Maybe::Check() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Check", "Maybe value is Nothing."); +} + +template +T Maybe::Unwrap() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Unwrap", "Maybe value is Nothing."); + return _value; +} + +template +T Maybe::UnwrapOr(const T& default_value) const { + return _has_value ? _value : default_value; +} + +template +bool Maybe::UnwrapTo(T* out) const { + if (IsJust()) { + *out = _value; + return true; + }; + return false; +} + +template +bool Maybe::operator==(const Maybe& other) const { + return (IsJust() == other.IsJust()) && + (!IsJust() || Unwrap() == other.Unwrap()); +} + +template +bool Maybe::operator!=(const Maybe& other) const { + return !operator==(other); +} + +template +Maybe::Maybe() : _has_value(false) {} + +template +Maybe::Maybe(const T& t) : _has_value(true), _value(t) {} + +template +inline Maybe Nothing() { + return Maybe(); +} + +template +inline Maybe Just(const T& t) { + return Maybe(t); +} + +//////////////////////////////////////////////////////////////////////////////// +// BasicEnv / Env class +//////////////////////////////////////////////////////////////////////////////// + +inline BasicEnv::BasicEnv(node_addon_api_basic_env env) : _env(env) {} + +inline BasicEnv::operator node_addon_api_basic_env() const { + return _env; +} + +inline Env::Env(napi_env env) : BasicEnv(env) {} + +inline Env::operator napi_env() const { + return const_cast(_env); +} + +inline Object Env::Global() const { + napi_value value; + napi_status status = napi_get_global(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Object()); + return Object(*this, value); +} + +inline Value Env::Undefined() const { + napi_value value; + napi_status status = napi_get_undefined(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Value()); + return Value(*this, value); +} + +inline Value Env::Null() const { + napi_value value; + napi_status status = napi_get_null(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Value()); + return Value(*this, value); +} + +inline bool Env::IsExceptionPending() const { + bool result; + napi_status status = napi_is_exception_pending(*this, &result); + if (status != napi_ok) + result = false; // Checking for a pending exception shouldn't throw. + return result; +} + +inline Error Env::GetAndClearPendingException() const { + napi_value value; + napi_status status = napi_get_and_clear_last_exception(*this, &value); + if (status != napi_ok) { + // Don't throw another exception when failing to get the exception! + return Error(); + } + return Error(*this, value); +} + +inline MaybeOrValue Env::RunScript(const char* utf8script) const { + String script = String::New(*this, utf8script); + return RunScript(script); +} + +inline MaybeOrValue Env::RunScript(const std::string& utf8script) const { + return RunScript(utf8script.c_str()); +} + +inline MaybeOrValue Env::RunScript(String script) const { + napi_value result; + napi_status status = napi_run_script(*this, script, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + *this, status, Napi::Value(*this, result), Napi::Value); +} + +#if NAPI_VERSION > 2 +template +void BasicEnv::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); + cleanupData->hook(); + delete cleanupData; +} + +template +void BasicEnv::CleanupHook::WrapperWithArg(void* data) + NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); + cleanupData->hook(static_cast(cleanupData->arg)); + delete cleanupData; +} +#endif // NAPI_VERSION > 2 + +#if NAPI_VERSION > 5 +template fini> +inline void BasicEnv::SetInstanceData(T* data) const { + napi_status status = napi_set_instance_data( + _env, + data, + [](napi_env env, void* data, void*) { fini(env, static_cast(data)); }, + nullptr); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); +} + +template fini> +inline void BasicEnv::SetInstanceData(DataType* data, HintType* hint) const { + napi_status status = napi_set_instance_data( + _env, + data, + [](napi_env env, void* data, void* hint) { + fini(env, static_cast(data), static_cast(hint)); + }, + hint); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); +} + +template +inline T* BasicEnv::GetInstanceData() const { + void* data = nullptr; + + napi_status status = napi_get_instance_data(_env, &data); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetInstanceData", "invalid arguments"); + + return static_cast(data); +} + +template +void BasicEnv::DefaultFini(Env, T* data) { + delete data; +} + +template +void BasicEnv::DefaultFiniWithHint(Env, DataType* data, HintType*) { + delete data; +} +#endif // NAPI_VERSION > 5 + +#if NAPI_VERSION > 8 +inline const char* BasicEnv::GetModuleFileName() const { + const char* result; + napi_status status = node_api_get_module_file_name(_env, &result); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetModuleFileName", "invalid arguments"); + return result; +} +#endif // NAPI_VERSION > 8 +//////////////////////////////////////////////////////////////////////////////// +// Value class +//////////////////////////////////////////////////////////////////////////////// + +inline Value::Value() : _env(nullptr), _value(nullptr) {} + +inline Value::Value(napi_env env, napi_value value) + : _env(env), _value(value) {} + +inline Value::operator napi_value() const { + return _value; +} + +inline bool Value::operator==(const Value& other) const { + return StrictEquals(other); +} + +inline bool Value::operator!=(const Value& other) const { + return !this->operator==(other); +} + +inline bool Value::StrictEquals(const Value& other) const { + bool result; + napi_status status = napi_strict_equals(_env, *this, other, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline Napi::Env Value::Env() const { + return Napi::Env(_env); +} + +inline bool Value::IsEmpty() const { + return _value == nullptr; +} + +inline napi_valuetype Value::Type() const { + if (IsEmpty()) { + return napi_undefined; + } + + napi_valuetype type; + napi_status status = napi_typeof(_env, _value, &type); + NAPI_THROW_IF_FAILED(_env, status, napi_undefined); + return type; +} + +inline bool Value::IsUndefined() const { + return Type() == napi_undefined; +} + +inline bool Value::IsNull() const { + return Type() == napi_null; +} + +inline bool Value::IsBoolean() const { + return Type() == napi_boolean; +} + +inline bool Value::IsNumber() const { + return Type() == napi_number; +} + +#if NAPI_VERSION > 5 +inline bool Value::IsBigInt() const { + return Type() == napi_bigint; +} +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +inline bool Value::IsDate() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_date(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} +#endif + +inline bool Value::IsString() const { + return Type() == napi_string; +} + +inline bool Value::IsSymbol() const { + return Type() == napi_symbol; +} + +inline bool Value::IsArray() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_array(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsArrayBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_arraybuffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsTypedArray() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_typedarray(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsObject() const { + return Type() == napi_object || IsFunction(); +} + +inline bool Value::IsFunction() const { + return Type() == napi_function; +} + +inline bool Value::IsPromise() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_promise(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsDataView() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_dataview(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_buffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsExternal() const { + return Type() == napi_external; +} + +template +inline T Value::As() const { +#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS + T::CheckCast(_env, _value); +#endif + return T(_env, _value); +} + +template +inline T Value::UnsafeAs() const { + return T(_env, _value); +} + +// static +inline void Value::CheckCast(napi_env /* env */, napi_value value) { + NAPI_CHECK(value != nullptr, "Value::CheckCast", "empty value"); +} + +inline MaybeOrValue Value::ToBoolean() const { + napi_value result; + napi_status status = napi_coerce_to_bool(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Boolean(_env, result), Napi::Boolean); +} + +inline MaybeOrValue Value::ToNumber() const { + napi_value result; + napi_status status = napi_coerce_to_number(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Number(_env, result), Napi::Number); +} + +inline MaybeOrValue Value::ToString() const { + napi_value result; + napi_status status = napi_coerce_to_string(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::String(_env, result), Napi::String); +} + +inline MaybeOrValue Value::ToObject() const { + napi_value result; + napi_status status = napi_coerce_to_object(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); +} + +//////////////////////////////////////////////////////////////////////////////// +// Boolean class +//////////////////////////////////////////////////////////////////////////////// + +inline Boolean Boolean::New(napi_env env, bool val) { + napi_value value; + napi_status status = napi_get_boolean(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Boolean()); + return Boolean(env, value); +} + +inline void Boolean::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Boolean::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Boolean::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_boolean, "%d", "Boolean::CheckCast"); +} + +inline Boolean::Boolean() : Napi::Value() {} + +inline Boolean::Boolean(napi_env env, napi_value value) + : Napi::Value(env, value) {} + +inline Boolean::operator bool() const { + return Value(); +} + +inline bool Boolean::Value() const { + bool result; + napi_status status = napi_get_value_bool(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Number class +//////////////////////////////////////////////////////////////////////////////// + +inline Number Number::New(napi_env env, double val) { + napi_value value; + napi_status status = napi_create_double(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Number()); + return Number(env, value); +} + +inline void Number::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Number::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Number::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_number, "%d", "Number::CheckCast"); +} + +inline Number::Number() : Value() {} + +inline Number::Number(napi_env env, napi_value value) : Value(env, value) {} + +inline Number::operator int32_t() const { + return Int32Value(); +} + +inline Number::operator uint32_t() const { + return Uint32Value(); +} + +inline Number::operator int64_t() const { + return Int64Value(); +} + +inline Number::operator float() const { + return FloatValue(); +} + +inline Number::operator double() const { + return DoubleValue(); +} + +inline int32_t Number::Int32Value() const { + int32_t result; + napi_status status = napi_get_value_int32(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline uint32_t Number::Uint32Value() const { + uint32_t result; + napi_status status = napi_get_value_uint32(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline int64_t Number::Int64Value() const { + int64_t result; + napi_status status = napi_get_value_int64(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline float Number::FloatValue() const { + return static_cast(DoubleValue()); +} + +inline double Number::DoubleValue() const { + double result; + napi_status status = napi_get_value_double(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// BigInt Class +//////////////////////////////////////////////////////////////////////////////// + +inline BigInt BigInt::New(napi_env env, int64_t val) { + napi_value value; + napi_status status = napi_create_bigint_int64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, uint64_t val) { + napi_value value; + napi_status status = napi_create_bigint_uint64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words) { + napi_value value; + napi_status status = + napi_create_bigint_words(env, sign_bit, word_count, words, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline void BigInt::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "BigInt::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "BigInt::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_bigint, "%d", "BigInt::CheckCast"); +} + +inline BigInt::BigInt() : Value() {} + +inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) {} + +inline int64_t BigInt::Int64Value(bool* lossless) const { + int64_t result; + napi_status status = + napi_get_value_bigint_int64(_env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline uint64_t BigInt::Uint64Value(bool* lossless) const { + uint64_t result; + napi_status status = + napi_get_value_bigint_uint64(_env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline size_t BigInt::WordCount() const { + size_t word_count; + napi_status status = + napi_get_value_bigint_words(_env, _value, nullptr, &word_count, nullptr); + NAPI_THROW_IF_FAILED(_env, status, 0); + return word_count; +} + +inline void BigInt::ToWords(int* sign_bit, + size_t* word_count, + uint64_t* words) { + napi_status status = + napi_get_value_bigint_words(_env, _value, sign_bit, word_count, words); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +//////////////////////////////////////////////////////////////////////////////// +// Date Class +//////////////////////////////////////////////////////////////////////////////// + +inline Date Date::New(napi_env env, double val) { + napi_value value; + napi_status status = napi_create_date(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Date()); + return Date(env, value); +} + +inline void Date::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Date::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_date(env, value, &result); + NAPI_CHECK(status == napi_ok, "Date::CheckCast", "napi_is_date failed"); + NAPI_CHECK(result, "Date::CheckCast", "value is not date"); +} + +inline Date::Date() : Value() {} + +inline Date::Date(napi_env env, napi_value value) : Value(env, value) {} + +inline Date::operator double() const { + return ValueOf(); +} + +inline double Date::ValueOf() const { + double result; + napi_status status = napi_get_date_value(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Name class +//////////////////////////////////////////////////////////////////////////////// +inline void Name::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Name::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Name::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK(type == napi_string || type == napi_symbol, + "Name::CheckCast", + "value is not napi_string or napi_symbol, got %d.", + type); +} + +inline Name::Name() : Value() {} + +inline Name::Name(napi_env env, napi_value value) : Value(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// String class +//////////////////////////////////////////////////////////////////////////////// + +inline String String::New(napi_env env, const std::string& val) { + return String::New(env, val.c_str(), val.size()); +} + +inline String String::New(napi_env env, const std::u16string& val) { + return String::New(env, val.c_str(), val.size()); +} + +inline String String::New(napi_env env, const char* val) { + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } + napi_value value; + napi_status status = + napi_create_string_utf8(env, val, std::strlen(val), &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char16_t* val) { + napi_value value; + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } + napi_status status = + napi_create_string_utf16(env, val, std::u16string(val).size(), &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char* val, size_t length) { + napi_value value; + napi_status status = napi_create_string_utf8(env, val, length, &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char16_t* val, size_t length) { + napi_value value; + napi_status status = napi_create_string_utf16(env, val, length, &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline void String::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "String::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "String::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_string, "%d", "String::CheckCast"); +} + +inline String::String() : Name() {} + +inline String::String(napi_env env, napi_value value) : Name(env, value) {} + +inline String::operator std::string() const { + return Utf8Value(); +} + +inline String::operator std::u16string() const { + return Utf16Value(); +} + +inline std::string String::Utf8Value() const { + size_t length; + napi_status status = + napi_get_value_string_utf8(_env, _value, nullptr, 0, &length); + NAPI_THROW_IF_FAILED(_env, status, ""); + + std::string value; + value.reserve(length + 1); + value.resize(length); + status = napi_get_value_string_utf8( + _env, _value, &value[0], value.capacity(), nullptr); + NAPI_THROW_IF_FAILED(_env, status, ""); + return value; +} + +inline std::u16string String::Utf16Value() const { + size_t length; + napi_status status = + napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); + + std::u16string value; + value.reserve(length + 1); + value.resize(length); + status = napi_get_value_string_utf16( + _env, _value, &value[0], value.capacity(), nullptr); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Symbol class +//////////////////////////////////////////////////////////////////////////////// + +inline Symbol Symbol::New(napi_env env, const char* description) { + napi_value descriptionValue = description != nullptr + ? String::New(env, description) + : static_cast(nullptr); + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, String description) { + napi_value descriptionValue = description; + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, napi_value description) { + napi_value value; + napi_status status = napi_create_symbol(env, description, &value); + NAPI_THROW_IF_FAILED(env, status, Symbol()); + return Symbol(env, value); +} + +inline MaybeOrValue Symbol::WellKnown(napi_env env, + const std::string& name) { + // No need to check if the return value is a symbol or undefined. + // Well known symbols are definite and it is an develop time error + // if the symbol does not exist. +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { + return Just(symbol_value.UnsafeAs()); + } + return Nothing(); +#else + return Napi::Env(env) + .Global() + .Get("Symbol") + .As() + .Get(name) + .UnsafeAs(); +#endif +} + +inline MaybeOrValue Symbol::For(napi_env env, + const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, const char* description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, String description) { + return Symbol::For(env, static_cast(description)); +} + +inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_for_value; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get("for").UnwrapTo(&symbol_for_value) && + symbol_for_value.As() + .Call(symbol_obj, {description}) + .UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else + Object symbol_obj = Napi::Env(env).Global().Get("Symbol").As(); + return symbol_obj.Get("for") + .As() + .Call(symbol_obj, {description}) + .As(); +#endif +} + +inline void Symbol::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Symbol::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Symbol::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_symbol, "%d", "Symbol::CheckCast"); +} + +inline Symbol::Symbol() : Name() {} + +inline Symbol::Symbol(napi_env env, napi_value value) : Name(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// Automagic value creation +//////////////////////////////////////////////////////////////////////////////// + +namespace details { +template +struct vf_number { + static Number From(napi_env env, T value) { + return Number::New(env, static_cast(value)); + } +}; + +template <> +struct vf_number { + static Boolean From(napi_env env, bool value) { + return Boolean::New(env, value); + } +}; + +struct vf_utf8_charp { + static String From(napi_env env, const char* value) { + return String::New(env, value); + } +}; + +struct vf_utf16_charp { + static String From(napi_env env, const char16_t* value) { + return String::New(env, value); + } +}; +struct vf_utf8_string { + static String From(napi_env env, const std::string& value) { + return String::New(env, value); + } +}; + +struct vf_utf16_string { + static String From(napi_env env, const std::u16string& value) { + return String::New(env, value); + } +}; + +template +struct vf_fallback { + static Value From(napi_env env, const T& value) { return Value(env, value); } +}; + +template +struct disjunction : std::false_type {}; +template +struct disjunction : B {}; +template +struct disjunction + : std::conditional>::type {}; + +template +struct can_make_string + : disjunction::type, + typename std::is_convertible::type, + typename std::is_convertible::type, + typename std::is_convertible::type> {}; +} // namespace details + +template +Value Value::From(napi_env env, const T& value) { + using Helper = typename std::conditional< + std::is_integral::value || std::is_floating_point::value, + details::vf_number, + typename std::conditional::value, + String, + details::vf_fallback>::type>::type; + return Helper::From(env, value); +} + +template +String String::From(napi_env env, const T& value) { + struct Dummy {}; + using Helper = typename std::conditional< + std::is_convertible::value, + details::vf_utf8_charp, + typename std::conditional< + std::is_convertible::value, + details::vf_utf16_charp, + typename std::conditional< + std::is_convertible::value, + details::vf_utf8_string, + typename std::conditional< + std::is_convertible::value, + details::vf_utf16_string, + Dummy>::type>::type>::type>::type; + return Helper::From(env, value); +} + +//////////////////////////////////////////////////////////////////////////////// +// TypeTaggable class +//////////////////////////////////////////////////////////////////////////////// + +inline TypeTaggable::TypeTaggable() : Value() {} + +inline TypeTaggable::TypeTaggable(napi_env _env, napi_value _value) + : Value(_env, _value) {} + +#if NAPI_VERSION >= 8 + +inline void TypeTaggable::TypeTag(const napi_type_tag* type_tag) const { + napi_status status = napi_type_tag_object(_env, _value, type_tag); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline bool TypeTaggable::CheckTypeTag(const napi_type_tag* type_tag) const { + bool result; + napi_status status = + napi_check_object_type_tag(_env, _value, type_tag, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +#endif // NAPI_VERSION >= 8 + +//////////////////////////////////////////////////////////////////////////////// +// Object class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object::PropertyLValue::operator Value() const { + MaybeOrValue val = Object(_env, _object).Get(_key); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + return val.Unwrap(); +#else + return val; +#endif +} + +template +template +inline Object::PropertyLValue& Object::PropertyLValue::operator=( + ValueType value) { +#ifdef NODE_ADDON_API_ENABLE_MAYBE + MaybeOrValue result = +#endif + Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + result.Unwrap(); +#endif + return *this; +} + +template +inline Object::PropertyLValue::PropertyLValue(Object object, Key key) + : _env(object.Env()), _object(object), _key(key) {} + +inline Object Object::New(napi_env env) { + napi_value value; + napi_status status = napi_create_object(env, &value); + NAPI_THROW_IF_FAILED(env, status, Object()); + return Object(env, value); +} + +inline void Object::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Object::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK(type == napi_object || type == napi_function, + "Object::CheckCast", + "Expect napi_object or napi_function, but got %d.", + type); +} + +inline Object::Object() : TypeTaggable() {} + +inline Object::Object(napi_env env, napi_value value) + : TypeTaggable(env, value) {} + +inline Object::PropertyLValue Object::operator[]( + const char* utf8name) { + return PropertyLValue(*this, utf8name); +} + +inline Object::PropertyLValue Object::operator[]( + const std::string& utf8name) { + return PropertyLValue(*this, utf8name); +} + +inline Object::PropertyLValue Object::operator[](uint32_t index) { + return PropertyLValue(*this, index); +} + +inline Object::PropertyLValue Object::operator[](Value index) const { + return PropertyLValue(*this, index); +} + +inline MaybeOrValue Object::operator[](const char* utf8name) const { + return Get(utf8name); +} + +inline MaybeOrValue Object::operator[]( + const std::string& utf8name) const { + return Get(utf8name); +} + +inline MaybeOrValue Object::operator[](uint32_t index) const { + return Get(index); +} + +inline MaybeOrValue Object::Has(napi_value key) const { + bool result; + napi_status status = napi_has_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(Value key) const { + bool result; + napi_status status = napi_has_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(const char* utf8name) const { + bool result; + napi_status status = napi_has_named_property(_env, _value, utf8name, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(const std::string& utf8name) const { + return Has(utf8name.c_str()); +} + +inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { + bool result; + napi_status status = napi_has_own_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::HasOwnProperty(Value key) const { + bool result; + napi_status status = napi_has_own_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { + napi_value key; + napi_status status = + napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); + NAPI_MAYBE_THROW_IF_FAILED(_env, status, bool); + return HasOwnProperty(key); +} + +inline MaybeOrValue Object::HasOwnProperty( + const std::string& utf8name) const { + return HasOwnProperty(utf8name.c_str()); +} + +inline MaybeOrValue Object::Get(napi_value key) const { + napi_value result; + napi_status status = napi_get_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(Value key) const { + napi_value result; + napi_status status = napi_get_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(const char* utf8name) const { + napi_value result; + napi_status status = napi_get_named_property(_env, _value, utf8name, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(const std::string& utf8name) const { + return Get(utf8name.c_str()); +} + +template +inline MaybeOrValue Object::Set(napi_value key, + const ValueType& value) const { + napi_status status = + napi_set_property(_env, _value, key, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(Value key, const ValueType& value) const { + napi_status status = + napi_set_property(_env, _value, key, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(const char* utf8name, + const ValueType& value) const { + napi_status status = + napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(const std::string& utf8name, + const ValueType& value) const { + return Set(utf8name.c_str(), value); +} + +inline MaybeOrValue Object::Delete(napi_value key) const { + bool result; + napi_status status = napi_delete_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Delete(Value key) const { + bool result; + napi_status status = napi_delete_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Delete(const char* utf8name) const { + return Delete(String::New(_env, utf8name)); +} + +inline MaybeOrValue Object::Delete(const std::string& utf8name) const { + return Delete(String::New(_env, utf8name)); +} + +inline MaybeOrValue Object::Has(uint32_t index) const { + bool result; + napi_status status = napi_has_element(_env, _value, index, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Get(uint32_t index) const { + napi_value value; + napi_status status = napi_get_element(_env, _value, index, &value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); +} + +template +inline MaybeOrValue Object::Set(uint32_t index, + const ValueType& value) const { + napi_status status = + napi_set_element(_env, _value, index, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Delete(uint32_t index) const { + bool result; + napi_status status = napi_delete_element(_env, _value, index, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::GetPropertyNames() const { + napi_value result; + napi_status status = napi_get_property_names(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); +} + +inline MaybeOrValue Object::DefineProperty( + const PropertyDescriptor& property) const { + napi_status status = napi_define_properties( + _env, + _value, + 1, + reinterpret_cast(&property)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::DefineProperties( + const std::initializer_list& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.begin())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::DefineProperties( + const std::vector& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.data())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::InstanceOf( + const Function& constructor) const { + bool result; + napi_status status = napi_instanceof(_env, _value, constructor, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + details::AttachData::Wrapper>( + _env, *this, data, finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = details:: + AttachData::WrapperWithHint>( + _env, *this, data, finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS +inline Object::const_iterator::const_iterator(const Object* object, + const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::const_iterator Napi::Object::begin() const { + const_iterator it(this, Object::const_iterator::Type::BEGIN); + return it; +} + +inline Object::const_iterator Napi::Object::end() const { + const_iterator it(this, Object::const_iterator::Type::END); + return it; +} + +inline Object::const_iterator& Object::const_iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::const_iterator::operator==( + const const_iterator& other) const { + return _index == other._index; +} + +inline bool Object::const_iterator::operator!=( + const const_iterator& other) const { + return _index != other._index; +} + +inline const std::pair> +Object::const_iterator::operator*() const { + const Value key = _keys[_index]; + const PropertyLValue value = (*_object)[key]; + return {key, value}; +} + +inline Object::iterator::iterator(Object* object, const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::iterator Napi::Object::begin() { + iterator it(this, Object::iterator::Type::BEGIN); + return it; +} + +inline Object::iterator Napi::Object::end() { + iterator it(this, Object::iterator::Type::END); + return it; +} + +inline Object::iterator& Object::iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::iterator::operator==(const iterator& other) const { + return _index == other._index; +} + +inline bool Object::iterator::operator!=(const iterator& other) const { + return _index != other._index; +} + +inline std::pair> +Object::iterator::operator*() { + Value key = _keys[_index]; + PropertyLValue value = (*_object)[key]; + return {key, value}; +} +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#if NAPI_VERSION >= 8 +inline MaybeOrValue Object::Freeze() const { + napi_status status = napi_object_freeze(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Seal() const { + napi_status status = napi_object_seal(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} +#endif // NAPI_VERSION >= 8 + +//////////////////////////////////////////////////////////////////////////////// +// External class +//////////////////////////////////////////////////////////////////////////////// + +template +inline External External::New(napi_env env, T* data) { + napi_value value; + napi_status status = + napi_create_external(env, data, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, External()); + return External(env, value); +} + +template +template +inline External External::New(napi_env env, + T* data, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external(env, + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, External()); + } + return External(env, value); +} + +template +template +inline External External::New(napi_env env, + T* data, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external( + env, + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, External()); + } + return External(env, value); +} + +template +inline void External::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "External::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "External::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_external, "%d", "External::CheckCast"); +} + +template +inline External::External() : TypeTaggable() {} + +template +inline External::External(napi_env env, napi_value value) + : TypeTaggable(env, value) {} + +template +inline T* External::Data() const { + void* data; + napi_status status = napi_get_value_external(_env, _value, &data); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return reinterpret_cast(data); +} + +//////////////////////////////////////////////////////////////////////////////// +// Array class +//////////////////////////////////////////////////////////////////////////////// + +inline Array Array::New(napi_env env) { + napi_value value; + napi_status status = napi_create_array(env, &value); + NAPI_THROW_IF_FAILED(env, status, Array()); + return Array(env, value); +} + +inline Array Array::New(napi_env env, size_t length) { + napi_value value; + napi_status status = napi_create_array_with_length(env, length, &value); + NAPI_THROW_IF_FAILED(env, status, Array()); + return Array(env, value); +} + +inline void Array::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Array::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_array(env, value, &result); + NAPI_CHECK(status == napi_ok, "Array::CheckCast", "napi_is_array failed"); + NAPI_CHECK(result, "Array::CheckCast", "value is not array"); +} + +inline Array::Array() : Object() {} + +inline Array::Array(napi_env env, napi_value value) : Object(env, value) {} + +inline uint32_t Array::Length() const { + uint32_t result; + napi_status status = napi_get_array_length(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// ArrayBuffer class +//////////////////////////////////////////////////////////////////////////////// + +inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) { + napi_value value; + void* data; + napi_status status = napi_create_arraybuffer(env, byteLength, &data, &value); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + + return ArrayBuffer(env, value); +} + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength) { + napi_value value; + napi_status status = napi_create_external_arraybuffer( + env, externalData, byteLength, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + + return ArrayBuffer(env, value); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = napi_create_external_arraybuffer( + env, + externalData, + byteLength, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + } + + return ArrayBuffer(env, value); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external_arraybuffer( + env, + externalData, + byteLength, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + } + + return ArrayBuffer(env, value); +} +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "ArrayBuffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_arraybuffer(env, value, &result); + NAPI_CHECK(status == napi_ok, + "ArrayBuffer::CheckCast", + "napi_is_arraybuffer failed"); + NAPI_CHECK(result, "ArrayBuffer::CheckCast", "value is not arraybuffer"); +} + +inline ArrayBuffer::ArrayBuffer() : Object() {} + +inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) + : Object(env, value) {} + +inline void* ArrayBuffer::Data() { + void* data; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return data; +} + +inline size_t ArrayBuffer::ByteLength() { + size_t length; + napi_status status = + napi_get_arraybuffer_info(_env, _value, nullptr, &length); + NAPI_THROW_IF_FAILED(_env, status, 0); + return length; +} + +#if NAPI_VERSION >= 7 +inline bool ArrayBuffer::IsDetached() const { + bool detached; + napi_status status = napi_is_detached_arraybuffer(_env, _value, &detached); + NAPI_THROW_IF_FAILED(_env, status, false); + return detached; +} + +inline void ArrayBuffer::Detach() { + napi_status status = napi_detach_arraybuffer(_env, _value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION >= 7 + +//////////////////////////////////////////////////////////////////////////////// +// DataView class +//////////////////////////////////////////////////////////////////////////////// +inline DataView DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer) { + return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset) { + if (byteOffset > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New( + env, "Start offset is outside the bounds of the buffer"), + DataView()); + } + return New( + env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength) { + if (byteOffset + byteLength > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); + } + napi_value value; + napi_status status = + napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); + NAPI_THROW_IF_FAILED(env, status, DataView()); + return DataView(env, value); +} + +inline void DataView::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_dataview(env, value, &result); + NAPI_CHECK( + status == napi_ok, "DataView::CheckCast", "napi_is_dataview failed"); + NAPI_CHECK(result, "DataView::CheckCast", "value is not dataview"); +} + +inline DataView::DataView() : Object() {} + +inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + &_length /* byteLength */, + &_data /* data */, + nullptr /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline Napi::ArrayBuffer DataView::ArrayBuffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + &arrayBuffer /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); + return Napi::ArrayBuffer(_env, arrayBuffer); +} + +inline size_t DataView::ByteOffset() const { + size_t byteOffset; + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + nullptr /* arrayBuffer */, + &byteOffset /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteOffset; +} + +inline size_t DataView::ByteLength() const { + return _length; +} + +inline void* DataView::Data() const { + return _data; +} + +inline float DataView::GetFloat32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline double DataView::GetFloat64(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int8_t DataView::GetInt8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int16_t DataView::GetInt16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int32_t DataView::GetInt32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint8_t DataView::GetUint8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint16_t DataView::GetUint16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint32_t DataView::GetUint32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline void DataView::SetFloat32(size_t byteOffset, float value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetFloat64(size_t byteOffset, double value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt8(size_t byteOffset, int8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt16(size_t byteOffset, int16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt32(size_t byteOffset, int32_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint8(size_t byteOffset, uint8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint16(size_t byteOffset, uint16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint32(size_t byteOffset, uint32_t value) const { + WriteData(byteOffset, value); +} + +template +inline T DataView::ReadData(size_t byteOffset) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW( + RangeError::New(_env, "Offset is outside the bounds of the DataView"), + 0); + } + + return *reinterpret_cast(static_cast(_data) + byteOffset); +} + +template +inline void DataView::WriteData(size_t byteOffset, T value) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW_VOID( + RangeError::New(_env, "Offset is outside the bounds of the DataView")); + } + + *reinterpret_cast(static_cast(_data) + byteOffset) = value; +} + +//////////////////////////////////////////////////////////////////////////////// +// TypedArray class +//////////////////////////////////////////////////////////////////////////////// +inline void TypedArray::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "TypedArray::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_typedarray(env, value, &result); + NAPI_CHECK( + status == napi_ok, "TypedArray::CheckCast", "napi_is_typedarray failed"); + NAPI_CHECK(result, "TypedArray::CheckCast", "value is not typedarray"); +} + +inline TypedArray::TypedArray() + : Object(), _type(napi_typedarray_type::napi_int8_array), _length(0) {} + +inline TypedArray::TypedArray(napi_env env, napi_value value) + : Object(env, value), + _type(napi_typedarray_type::napi_int8_array), + _length(0) { + if (value != nullptr) { + napi_status status = + napi_get_typedarray_info(_env, + _value, + &const_cast(this)->_type, + &const_cast(this)->_length, + nullptr, + nullptr, + nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +inline TypedArray::TypedArray(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length) + : Object(env, value), _type(type), _length(length) {} + +inline napi_typedarray_type TypedArray::TypedArrayType() const { + return _type; +} + +inline uint8_t TypedArray::ElementSize() const { + switch (_type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: +#if (NAPI_VERSION > 5) + case napi_bigint64_array: + case napi_biguint64_array: +#endif // (NAPI_VERSION > 5) + return 8; + default: + return 0; + } +} + +inline size_t TypedArray::ElementLength() const { + return _length; +} + +inline size_t TypedArray::ByteOffset() const { + size_t byteOffset; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, nullptr, &byteOffset); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteOffset; +} + +inline size_t TypedArray::ByteLength() const { + return ElementSize() * ElementLength(); +} + +inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); + return Napi::ArrayBuffer(_env, arrayBuffer); +} + +//////////////////////////////////////////////////////////////////////////////// +// TypedArrayOf class +//////////////////////////////////////////////////////////////////////////////// +template +inline void TypedArrayOf::CheckCast(napi_env env, napi_value value) { + TypedArray::CheckCast(env, value); + napi_typedarray_type type; + napi_status status = napi_get_typedarray_info( + env, value, &type, nullptr, nullptr, nullptr, nullptr); + NAPI_CHECK(status == napi_ok, + "TypedArrayOf::CheckCast", + "napi_is_typedarray failed"); + + NAPI_INTERNAL_CHECK( + (type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && std::is_same::value)), + "TypedArrayOf::CheckCast", + "Array type must match the template parameter, (Uint8 arrays may " + "optionally have the \"clamped\" array type.), got %d.", + type); +} + +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + napi_typedarray_type type) { + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, elementLength * sizeof(T)); + return New(env, elementLength, arrayBuffer, 0, type); +} + +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::ArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type) { + napi_value value; + napi_status status = napi_create_typedarray( + env, type, elementLength, arrayBuffer, bufferOffset, &value); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); + + return TypedArrayOf( + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); +} + +template +inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} + +template +inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) + : TypedArray(env, value), _data(nullptr) { + napi_status status = napi_ok; + if (value != nullptr) { + void* data = nullptr; + status = napi_get_typedarray_info( + _env, _value, &_type, &_length, &data, nullptr, nullptr); + _data = static_cast(data); + } else { + _type = TypedArrayTypeForPrimitiveType(); + _length = 0; + } + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +template +inline TypedArrayOf::TypedArrayOf(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length, + T* data) + : TypedArray(env, value, type, length), _data(data) { + if (!(type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && + std::is_same::value))) { + NAPI_THROW_VOID(TypeError::New( + env, + "Array type must match the template parameter. " + "(Uint8 arrays may optionally have the \"clamped\" array type.)")); + } +} + +template +inline T& TypedArrayOf::operator[](size_t index) { + return _data[index]; +} + +template +inline const T& TypedArrayOf::operator[](size_t index) const { + return _data[index]; +} + +template +inline T* TypedArrayOf::Data() { + return _data; +} + +template +inline const T* TypedArrayOf::Data() const { + return _data; +} + +//////////////////////////////////////////////////////////////////////////////// +// Function class +//////////////////////////////////////////////////////////////////////////////// + +template +inline napi_status CreateFunction(napi_env env, + const char* utf8name, + napi_callback cb, + CbData* data, + napi_value* result) { + napi_status status = + napi_create_function(env, utf8name, NAPI_AUTO_LENGTH, cb, data, result); + if (status == napi_ok) { + status = Napi::details::AttachData(env, *result, data); + } + + return status; +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedVoidCallback, + data, + &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedCallback, + data, + &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + Callable cb, + const char* utf8name, + void* data) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + auto callbackData = new CbData{std::move(cb), data}; + + napi_value value; + napi_status status = + CreateFunction(env, utf8name, CbData::Wrapper, callbackData, &value); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, Function()); + } + + return Function(env, value); +} + +template +inline Function Function::New(napi_env env, + Callable cb, + const std::string& utf8name, + void* data) { + return New(env, cb, utf8name.c_str(), data); +} + +inline void Function::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Function::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Function::CheckCast", "napi_typeof failed"); + NAPI_INTERNAL_CHECK_EQ(type, napi_function, "%d", "Function::CheckCast"); +} + +inline Function::Function() : Object() {} + +inline Function::Function(napi_env env, napi_value value) + : Object(env, value) {} + +inline MaybeOrValue Function::operator()( + const std::initializer_list& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::initializer_list& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::vector& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::vector& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call(size_t argc, + const napi_value* args) const { + return Call(Env().Undefined(), argc, args); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::initializer_list& args) const { + return Call(recv, args.size(), args.begin()); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { + return Call(recv, args.size(), args.data()); +} + +inline MaybeOrValue Function::Call(napi_value recv, const std::vector& args) const { + const size_t argc = args.size(); + const size_t stackArgsCount = 6; + napi_value stackArgs[stackArgsCount]; + std::vector heapArgs; + napi_value* argv; + if (argc <= stackArgsCount) { + argv = stackArgs; + } else { + heapArgs.resize(argc); + argv = heapArgs.data(); + } + + for (size_t index = 0; index < argc; index++) { + argv[index] = static_cast(args[index]); + } + + return Call(recv, argc, argv); +} + +inline MaybeOrValue Function::Call(napi_value recv, size_t argc, const napi_value* args) const { + napi_value result; + napi_status status = napi_call_function(_env, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Napi::Value(_env, result), Napi::Value); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.begin(), context); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.data(), context); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { + napi_value result; + napi_status status = + napi_make_callback(_env, context, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); +} + +inline MaybeOrValue Function::New( + const std::initializer_list& args) const { + return New(args.size(), args.begin()); +} + +inline MaybeOrValue Function::New( + const std::vector& args) const { + return New(args.size(), args.data()); +} + +inline MaybeOrValue Function::New(size_t argc, + const napi_value* args) const { + napi_value result; + napi_status status = napi_new_instance(_env, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); +} + +//////////////////////////////////////////////////////////////////////////////// +// Promise class +//////////////////////////////////////////////////////////////////////////////// + +inline Promise::Deferred Promise::Deferred::New(napi_env env) { + return Promise::Deferred(env); +} + +inline Promise::Deferred::Deferred(napi_env env) : _env(env) { + napi_status status = napi_create_promise(_env, &_deferred, &_promise); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline Promise Promise::Deferred::Promise() const { + return Napi::Promise(_env, _promise); +} + +inline Napi::Env Promise::Deferred::Env() const { + return Napi::Env(_env); +} + +inline void Promise::Deferred::Resolve(napi_value value) const { + napi_status status = napi_resolve_deferred(_env, _deferred, value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Promise::Deferred::Reject(napi_value value) const { + napi_status status = napi_reject_deferred(_env, _deferred, value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Promise::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Promise::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_promise(env, value, &result); + NAPI_CHECK(status == napi_ok, "Promise::CheckCast", "napi_is_promise failed"); + NAPI_CHECK(result, "Promise::CheckCast", "value is not promise"); +} + +inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// Buffer class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Buffer Buffer::New(napi_env env, size_t length) { + napi_value value; + void* data; + napi_status status = + napi_create_buffer(env, length * sizeof(T), &data, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +template +inline Buffer Buffer::New(napi_env env, T* data, size_t length) { + napi_value value; + napi_status status = napi_create_external_buffer( + env, length * sizeof(T), data, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +} + +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external_buffer( + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +} +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +template +inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length) { +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = napi_create_external_buffer( + env, length * sizeof(T), data, nullptr, nullptr, &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + return Buffer::Copy(env, data, length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::WrapperGC(env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = napi_create_external_buffer( + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::WrapperGCWithHint( + env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif +} + +template +inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { + napi_value value; + napi_status status = + napi_create_buffer_copy(env, length * sizeof(T), data, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +template +inline void Buffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_buffer(env, value, &result); + NAPI_CHECK(status == napi_ok, "Buffer::CheckCast", "napi_is_buffer failed"); + NAPI_CHECK(result, "Buffer::CheckCast", "value is not buffer"); +} + +template +inline Buffer::Buffer() : Uint8Array() {} + +template +inline Buffer::Buffer(napi_env env, napi_value value) + : Uint8Array(env, value) {} + +template +inline size_t Buffer::Length() const { + return ByteLength() / sizeof(T); +} + +template +inline T* Buffer::Data() const { + return reinterpret_cast(const_cast(Uint8Array::Data())); +} + +//////////////////////////////////////////////////////////////////////////////// +// Error class +//////////////////////////////////////////////////////////////////////////////// + +inline Error Error::New(napi_env env) { + napi_status status; + napi_value error = nullptr; + bool is_exception_pending; + napi_extended_error_info last_error_info_copy; + + { + // We must retrieve the last error info before doing anything else because + // doing anything else will replace the last error info. + const napi_extended_error_info* last_error_info; + status = napi_get_last_error_info(env, &last_error_info); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); + + // All fields of the `napi_extended_error_info` structure gets reset in + // subsequent Node-API function calls on the same `env`. This includes a + // call to `napi_is_exception_pending()`. So here it is necessary to make a + // copy of the information as the `error_code` field is used later on. + memcpy(&last_error_info_copy, + last_error_info, + sizeof(napi_extended_error_info)); + } + + status = napi_is_exception_pending(env, &is_exception_pending); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); + + // A pending exception takes precedence over any internal error status. + if (is_exception_pending) { + status = napi_get_and_clear_last_exception(env, &error); + NAPI_FATAL_IF_FAILED( + status, "Error::New", "napi_get_and_clear_last_exception"); + } else { + const char* error_message = last_error_info_copy.error_message != nullptr + ? last_error_info_copy.error_message + : "Error in native callback"; + + napi_value message; + status = napi_create_string_utf8( + env, error_message, std::strlen(error_message), &message); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); + + switch (last_error_info_copy.error_code) { + case napi_object_expected: + case napi_string_expected: + case napi_boolean_expected: + case napi_number_expected: + status = napi_create_type_error(env, nullptr, message, &error); + break; + default: + status = napi_create_error(env, nullptr, message, &error); + break; + } + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); + } + + return Error(env, error); +} + +inline Error Error::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_error); +} + +inline Error Error::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_error); +} + +inline NAPI_NO_RETURN void Error::Fatal(const char* location, + const char* message) { + napi_fatal_error(location, NAPI_AUTO_LENGTH, message, NAPI_AUTO_LENGTH); +} + +inline Error::Error() : ObjectReference() {} + +inline Error::Error(napi_env env, napi_value value) + : ObjectReference(env, nullptr) { + if (value != nullptr) { + // Attempting to create a reference on the error object. + // If it's not a Object/Function/Symbol, this call will return an error + // status. + napi_status status = napi_create_reference(env, value, 1, &_ref); + + if (status != napi_ok) { + napi_value wrappedErrorObj; + + // Create an error object + status = napi_create_object(env, &wrappedErrorObj); + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_object"); + + // property flag that we attach to show the error object is wrapped + napi_property_descriptor wrapObjFlag = { + ERROR_WRAP_VALUE(), // Unique GUID identifier since Symbol isn't a + // viable option + nullptr, + nullptr, + nullptr, + nullptr, + Value::From(env, value), + napi_enumerable, + nullptr}; + + status = napi_define_properties(env, wrappedErrorObj, 1, &wrapObjFlag); +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + if (status == napi_pending_exception) { + // Test if the pending exception was reported because the environment is + // shutting down. We assume that a status of napi_pending_exception + // coupled with the absence of an actual pending exception means that + // the environment is shutting down. If so, we replace the + // napi_pending_exception status with napi_ok. + bool is_exception_pending = false; + status = napi_is_exception_pending(env, &is_exception_pending); + if (status == napi_ok && !is_exception_pending) { + status = napi_ok; + } else { + status = napi_pending_exception; + } + } +#endif // NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_define_properties"); + + // Create a reference on the newly wrapped object + status = napi_create_reference(env, wrappedErrorObj, 1, &_ref); + } + + // Avoid infinite recursion in the failure case. + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference"); + } +} + +inline Object Error::Value() const { + if (_ref == nullptr) { + return Object(_env, nullptr); + } + + napi_value refValue; + napi_status status = napi_get_reference_value(_env, _ref, &refValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + napi_valuetype type; + status = napi_typeof(_env, refValue, &type); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + // If refValue isn't a symbol, then we proceed to whether the refValue has the + // wrapped error flag + if (type != napi_symbol) { + // We are checking if the object is wrapped + bool isWrappedObject = false; + + status = napi_has_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &isWrappedObject); + + // Don't care about status + if (isWrappedObject) { + napi_value unwrappedValue; + status = napi_get_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &unwrappedValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + return Object(_env, unwrappedValue); + } + } + + return Object(_env, refValue); +} + +inline Error::Error(Error&& other) : ObjectReference(std::move(other)) {} + +inline Error& Error::operator=(Error&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline Error::Error(const Error& other) : ObjectReference(other) {} + +inline Error& Error::operator=(const Error& other) { + Reset(); + + _env = other.Env(); + HandleScope scope(_env); + + napi_value value = other.Value(); + if (value != nullptr) { + napi_status status = napi_create_reference(_env, value, 1, &_ref); + NAPI_THROW_IF_FAILED(_env, status, *this); + } + + return *this; +} + +inline const std::string& Error::Message() const NAPI_NOEXCEPT { + if (_message.size() == 0 && _env != nullptr) { +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + _message = Get("message").As(); + } catch (...) { + // Catch all errors here, to include e.g. a std::bad_alloc from + // the std::string::operator=, because this method may not throw. + } +#else // NODE_ADDON_API_CPP_EXCEPTIONS +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Napi::Value message_val; + if (Get("message").UnwrapTo(&message_val)) { + _message = message_val.As(); + } +#else + _message = Get("message").As(); +#endif +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + } + return _message; +} + +// we created an object on the &_ref +inline void Error::ThrowAsJavaScriptException() const { + HandleScope scope(_env); + if (!IsEmpty()) { +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + bool pendingException = false; + + // check if there is already a pending exception. If so don't try to throw a + // new one as that is not allowed/possible + napi_status status = napi_is_exception_pending(_env, &pendingException); + + if ((status != napi_ok) || + ((status == napi_ok) && (pendingException == false))) { + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + status = napi_throw(_env, Value()); + +#if (NAPI_VERSION >= 10) + napi_status expected_failure_mode = napi_cannot_run_js; +#else + napi_status expected_failure_mode = napi_pending_exception; +#endif + if (status == expected_failure_mode) { + // The environment must be terminating as we checked earlier and there + // was no pending exception. In this case continuing will result + // in a fatal error and there is nothing the author has done incorrectly + // in their code that is worth flagging through a fatal error + return; + } + } else { + status = napi_pending_exception; + } +#else + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + napi_status status = napi_throw(_env, Value()); +#endif + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + if (status != napi_ok) { + throw Error::New(_env); + } +#else // NODE_ADDON_API_CPP_EXCEPTIONS + NAPI_FATAL_IF_FAILED( + status, "Error::ThrowAsJavaScriptException", "napi_throw"); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + } +} + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + +inline const char* Error::what() const NAPI_NOEXCEPT { + return Message().c_str(); +} + +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +inline const char* Error::ERROR_WRAP_VALUE() NAPI_NOEXCEPT { + return "4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal"; +} + +template +inline TError Error::New(napi_env env, + const char* message, + size_t length, + create_error_fn create_error) { + napi_value str; + napi_status status = napi_create_string_utf8(env, message, length, &str); + NAPI_THROW_IF_FAILED(env, status, TError()); + + napi_value error; + status = create_error(env, nullptr, str, &error); + NAPI_THROW_IF_FAILED(env, status, TError()); + + return TError(env, error); +} + +inline TypeError TypeError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_type_error); +} + +inline TypeError TypeError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_type_error); +} + +inline TypeError::TypeError() : Error() {} + +inline TypeError::TypeError(napi_env env, napi_value value) + : Error(env, value) {} + +inline RangeError RangeError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_range_error); +} + +inline RangeError RangeError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_range_error); +} + +inline RangeError::RangeError() : Error() {} + +inline RangeError::RangeError(napi_env env, napi_value value) + : Error(env, value) {} + +#if NAPI_VERSION > 8 +inline SyntaxError SyntaxError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), node_api_create_syntax_error); +} + +inline SyntaxError SyntaxError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), node_api_create_syntax_error); +} + +inline SyntaxError::SyntaxError() : Error() {} + +inline SyntaxError::SyntaxError(napi_env env, napi_value value) + : Error(env, value) {} +#endif // NAPI_VERSION > 8 + +//////////////////////////////////////////////////////////////////////////////// +// Reference class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Reference Reference::New(const T& value, + uint32_t initialRefcount) { + napi_env env = value.Env(); + napi_value val = value; + + if (val == nullptr) { + return Reference(env, nullptr); + } + + napi_ref ref; + napi_status status = napi_create_reference(env, value, initialRefcount, &ref); + NAPI_THROW_IF_FAILED(env, status, Reference()); + + return Reference(env, ref); +} + +template +inline Reference::Reference() + : _env(nullptr), _ref(nullptr), _suppressDestruct(false) {} + +template +inline Reference::Reference(napi_env env, napi_ref ref) + : _env(env), _ref(ref), _suppressDestruct(false) {} + +template +inline Reference::~Reference() { + if (_ref != nullptr) { + if (!_suppressDestruct) { + // TODO(legendecas): napi_delete_reference should be invoked immediately. + // Fix this when https://github.com/nodejs/node/pull/55620 lands. +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + Env().PostFinalizer( + [](Napi::Env env, napi_ref ref) { napi_delete_reference(env, ref); }, + _ref); +#else + napi_delete_reference(_env, _ref); +#endif + } + + _ref = nullptr; + } +} + +template +inline Reference::Reference(Reference&& other) + : _env(other._env), + _ref(other._ref), + _suppressDestruct(other._suppressDestruct) { + other._env = nullptr; + other._ref = nullptr; + other._suppressDestruct = false; +} + +template +inline Reference& Reference::operator=(Reference&& other) { + Reset(); + _env = other._env; + _ref = other._ref; + _suppressDestruct = other._suppressDestruct; + other._env = nullptr; + other._ref = nullptr; + other._suppressDestruct = false; + return *this; +} + +template +inline Reference::Reference(const Reference& other) + : _env(other._env), _ref(nullptr), _suppressDestruct(false) { + HandleScope scope(_env); + + napi_value value = other.Value(); + if (value != nullptr) { + // Copying is a limited scenario (currently only used for Error object) and + // always creates a strong reference to the given value even if the incoming + // reference is weak. + napi_status status = napi_create_reference(_env, value, 1, &_ref); + NAPI_FATAL_IF_FAILED( + status, "Reference::Reference", "napi_create_reference"); + } +} + +template +inline Reference::operator napi_ref() const { + return _ref; +} + +template +inline bool Reference::operator==(const Reference& other) const { + HandleScope scope(_env); + return this->Value().StrictEquals(other.Value()); +} + +template +inline bool Reference::operator!=(const Reference& other) const { + return !this->operator==(other); +} + +template +inline Napi::Env Reference::Env() const { + return Napi::Env(_env); +} + +template +inline bool Reference::IsEmpty() const { + return _ref == nullptr; +} + +template +inline T Reference::Value() const { + if (_ref == nullptr) { + return T(_env, nullptr); + } + + napi_value value; + napi_status status = napi_get_reference_value(_env, _ref, &value); + NAPI_THROW_IF_FAILED(_env, status, T()); + return T(_env, value); +} + +template +inline uint32_t Reference::Ref() const { + uint32_t result; + napi_status status = napi_reference_ref(_env, _ref, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +template +inline uint32_t Reference::Unref() const { + uint32_t result; + napi_status status = napi_reference_unref(_env, _ref, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +template +inline void Reference::Reset() { + if (_ref != nullptr) { + napi_status status = napi_delete_reference(_env, _ref); + NAPI_THROW_IF_FAILED_VOID(_env, status); + _ref = nullptr; + } +} + +template +inline void Reference::Reset(const T& value, uint32_t refcount) { + Reset(); + _env = value.Env(); + + napi_value val = value; + if (val != nullptr) { + napi_status status = napi_create_reference(_env, value, refcount, &_ref); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Reference::SuppressDestruct() { + _suppressDestruct = true; +} + +template +inline Reference Weak(T value) { + return Reference::New(value, 0); +} + +inline ObjectReference Weak(Object value) { + return Reference::New(value, 0); +} + +inline FunctionReference Weak(Function value) { + return Reference::New(value, 0); +} + +template +inline Reference Persistent(T value) { + return Reference::New(value, 1); +} + +inline ObjectReference Persistent(Object value) { + return Reference::New(value, 1); +} + +inline FunctionReference Persistent(Function value) { + return Reference::New(value, 1); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectReference class +//////////////////////////////////////////////////////////////////////////////// + +inline ObjectReference::ObjectReference() : Reference() {} + +inline ObjectReference::ObjectReference(napi_env env, napi_ref ref) + : Reference(env, ref) {} + +inline ObjectReference::ObjectReference(Reference&& other) + : Reference(std::move(other)) {} + +inline ObjectReference& ObjectReference::operator=(Reference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline ObjectReference::ObjectReference(ObjectReference&& other) + : Reference(std::move(other)) {} + +inline ObjectReference& ObjectReference::operator=(ObjectReference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline ObjectReference::ObjectReference(const ObjectReference& other) + : Reference(other) {} + +inline MaybeOrValue ObjectReference::Get( + const char* utf8name) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Get( + const std::string& utf8name) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + const char* utf8value) const { + HandleScope scope(_env); + return Value().Set(utf8name, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, numberValue); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + std::string& utf8value) const { + HandleScope scope(_env); + return Value().Set(utf8name, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, numberValue); +} + +inline MaybeOrValue ObjectReference::Get(uint32_t index) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(index); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(index, value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(index, value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const char* utf8value) const { + HandleScope scope(_env); + return Value().Set(index, utf8value); +} + +inline MaybeOrValue ObjectReference::Set( + uint32_t index, const std::string& utf8value) const { + HandleScope scope(_env); + return Value().Set(index, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(index, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(index, numberValue); +} + +//////////////////////////////////////////////////////////////////////////////// +// FunctionReference class +//////////////////////////////////////////////////////////////////////////////// + +inline FunctionReference::FunctionReference() : Reference() {} + +inline FunctionReference::FunctionReference(napi_env env, napi_ref ref) + : Reference(env, ref) {} + +inline FunctionReference::FunctionReference(Reference&& other) + : Reference(std::move(other)) {} + +inline FunctionReference& FunctionReference::operator=( + Reference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline FunctionReference::FunctionReference(FunctionReference&& other) + : Reference(std::move(other)) {} + +inline FunctionReference& FunctionReference::operator=( + FunctionReference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline MaybeOrValue FunctionReference::operator()( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value()(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, size_t argc, const napi_value* args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, argc, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = + Value().MakeCallback(recv, argc, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::New( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue FunctionReference::New( + const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// CallbackInfo class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) + : _env(env), + _info(info), + _this(nullptr), + _dynamicArgs(nullptr), + _data(nullptr) { + _argc = _staticArgCount; + _argv = _staticArgs; + napi_status status = + napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + if (_argc > _staticArgCount) { + // Use either a fixed-size array (on the stack) or a dynamically-allocated + // array (on the heap) depending on the number of args. + _dynamicArgs = new napi_value[_argc]; + _argv = _dynamicArgs; + + status = napi_get_cb_info(env, info, &_argc, _argv, nullptr, nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +inline CallbackInfo::~CallbackInfo() { + if (_dynamicArgs != nullptr) { + delete[] _dynamicArgs; + } +} + +inline CallbackInfo::operator napi_callback_info() const { + return _info; +} + +inline Value CallbackInfo::NewTarget() const { + napi_value newTarget; + napi_status status = napi_get_new_target(_env, _info, &newTarget); + NAPI_THROW_IF_FAILED(_env, status, Value()); + return Value(_env, newTarget); +} + +inline bool CallbackInfo::IsConstructCall() const { + return !NewTarget().IsEmpty(); +} + +inline Napi::Env CallbackInfo::Env() const { + return Napi::Env(_env); +} + +inline size_t CallbackInfo::Length() const { + return _argc; +} + +inline const Value CallbackInfo::operator[](size_t index) const { + return index < _argc ? Value(_env, _argv[index]) : Env().Undefined(); +} + +inline Value CallbackInfo::This() const { + if (_this == nullptr) { + return Env().Undefined(); + } + return Object(_env, _this); +} + +inline void* CallbackInfo::Data() const { + return _data; +} + +inline void CallbackInfo::SetData(void* data) { + _data = data; +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyDescriptor class +//////////////////////////////////////////////////////////////////////////////// + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(env, object, utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor( + env, object, utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, utf8name, data), + attributes, + nullptr}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(env, object, utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({nullptr, + name, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, nullptr, data), + attributes, + nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + value, + attributes, + nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes) { + return Value(utf8name.c_str(), value, attributes); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + napi_value name, napi_value value, napi_property_attributes attributes) { + return PropertyDescriptor( + {nullptr, name, nullptr, nullptr, nullptr, value, attributes, nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + Name name, Napi::Value value, napi_property_attributes attributes) { + napi_value nameValue = name; + napi_value valueValue = value; + return PropertyDescriptor::Value(nameValue, valueValue, attributes); +} + +inline PropertyDescriptor::PropertyDescriptor(napi_property_descriptor desc) + : _desc(desc) {} + +inline PropertyDescriptor::operator napi_property_descriptor&() { + return _desc; +} + +inline PropertyDescriptor::operator const napi_property_descriptor&() const { + return _desc; +} + +//////////////////////////////////////////////////////////////////////////////// +// InstanceWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline void InstanceWrap::AttachPropData( + napi_env env, napi_value value, const napi_property_descriptor* prop) { + napi_status status; + if (!(prop->attributes & napi_static)) { + if (prop->method == T::InstanceVoidMethodCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->method == T::InstanceMethodCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->getter == T::InstanceGetterCallbackWrapper || + prop->setter == T::InstanceSetterCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } + } +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::InstanceMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::InstanceMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedInstanceVoidCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedInstanceCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedInstanceVoidCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedInstanceCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = details::TemplatedInstanceCallback; + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = details::TemplatedInstanceCallback; + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + Symbol name, Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + if (instance) (instance->*cb)(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); + }); +} + +template +inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->getterCallback; + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); + }); +} + +template +inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->setterCallback; + if (instance) (instance->*cb)(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +template ::InstanceSetterCallback method> +inline napi_value InstanceWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + if (instance) (instance->*method)(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { + napi_env env = callbackInfo.Env(); + napi_value wrapper = callbackInfo.This(); + napi_status status; + napi_ref ref; + T* instance = static_cast(this); + status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); + NAPI_THROW_IF_FAILED_VOID(env, status); + + Reference* instanceRef = instance; + *instanceRef = Reference(env, ref); +} + +template +inline ObjectWrap::~ObjectWrap() { + // If the JS object still exists at this point, remove the finalizer added + // through `napi_wrap()`. + if (!IsEmpty() && !_finalized) { + Object object = Value(); + // It is not valid to call `napi_remove_wrap()` with an empty `object`. + // This happens e.g. during garbage collection. + if (!object.IsEmpty() && _construction_failed) { + napi_remove_wrap(Env(), object, nullptr); + } + } +} + +template +inline T* ObjectWrap::Unwrap(Object wrapper) { + void* unwrapped; + napi_status status = napi_unwrap(wrapper.Env(), wrapper, &unwrapped); + NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); + return static_cast(unwrapped); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* descriptors, + void* data) { + napi_status status; + std::vector props(props_count); + + // We copy the descriptors to a local array because before defining the class + // we must replace static method property descriptors with value property + // descriptors such that the value is a function-valued `napi_value` created + // with `CreateFunction()`. + // + // This replacement could be made for instance methods as well, but V8 aborts + // if we do that, because it expects methods defined on the prototype template + // to have `FunctionTemplate`s. + for (size_t index = 0; index < props_count; index++) { + props[index] = descriptors[index]; + napi_property_descriptor* prop = &props[index]; + if (prop->method == T::StaticMethodCallbackWrapper) { + status = + CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { + status = + CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } + } + + napi_value value; + status = napi_define_class(env, + utf8name, + NAPI_AUTO_LENGTH, + T::ConstructorCallbackWrapper, + data, + props_count, + props.data(), + &value); + NAPI_THROW_IF_FAILED(env, status, Function()); + + // After defining the class we iterate once more over the property descriptors + // and attach the data associated with accessors and instance methods to the + // newly created JavaScript class. + for (size_t idx = 0; idx < props_count; idx++) { + const napi_property_descriptor* prop = &props[idx]; + + if (prop->getter == T::StaticGetterCallbackWrapper || + prop->setter == T::StaticSetterCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else { + // InstanceWrap::AttachPropData is responsible for attaching the data + // of instance methods and accessors. + T::AttachPropData(env, value, prop); + } + } + + return Function(env, value); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list>& properties, + void* data) { + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::vector>& properties, + void* data) { + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedVoidCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedVoidCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.value = value; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + Symbol name, Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline Value ObjectWrap::OnCalledAsFunction( + const Napi::CallbackInfo& callbackInfo) { + NAPI_THROW( + TypeError::New(callbackInfo.Env(), + "Class constructors cannot be invoked without 'new'"), + Napi::Value()); +} + +template +inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} + +template +inline void ObjectWrap::Finalize(BasicEnv /*env*/) {} + +template +inline napi_value ObjectWrap::ConstructorCallbackWrapper( + napi_env env, napi_callback_info info) { + napi_value new_target; + napi_status status = napi_get_new_target(env, info, &new_target); + if (status != napi_ok) return nullptr; + + bool isConstructCall = (new_target != nullptr); + if (!isConstructCall) { + return details::WrapCallback( + env, [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); + } + + napi_value wrapper = details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + T* instance = new T(callbackInfo); +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + instance->_construction_failed = false; +#else + if (callbackInfo.Env().IsExceptionPending()) { + // We need to clear the exception so that removing the wrap might work. + Error e = callbackInfo.Env().GetAndClearPendingException(); + delete instance; + e.ThrowAsJavaScriptException(); + } else { + instance->_construction_failed = false; + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + return callbackInfo.This(); + }); + + return wrapper; +} + +template +inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->callback(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value ObjectWrap::StaticMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->callback(callbackInfo); + }); +} + +template +inline napi_value ObjectWrap::StaticGetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->getterCallback(callbackInfo); + }); +} + +template +inline napi_value ObjectWrap::StaticSetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback(env, [&] { + CallbackInfo callbackInfo(env, info); + StaticAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->setterCallback(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +inline void ObjectWrap::FinalizeCallback(node_addon_api_basic_env env, + void* data, + void* /*hint*/) { + // If the child class does not override _any_ Finalize() method, `env` will be + // unused because of the constexpr guards. Explicitly reference it here to + // bypass compiler warnings. + (void)env; + T* instance = static_cast(data); + + // Prevent ~ObjectWrap from calling napi_remove_wrap. + // The instance->_ref should be deleted with napi_delete_reference in + // ~Reference. + instance->_finalized = true; + + // If class overrides the basic finalizer, execute it. + if constexpr (details::HasBasicFinalizer::value) { +#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + HandleScope scope(env); +#endif + + instance->Finalize(Napi::BasicEnv(env)); + } + + // If class overrides the (extended) finalizer, either schedule it or + // execute it immediately (depending on experimental features enabled). + if constexpr (details::HasExtendedFinalizer::value) { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + // In experimental, attach via node_api_post_finalizer. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + napi_status status = + node_api_post_finalizer(env, PostFinalizeCallback, data, nullptr); + NAPI_FATAL_IF_FAILED(status, + "ObjectWrap::FinalizeCallback", + "node_api_post_finalizer failed"); +#else + // In non-experimental, this `FinalizeCallback` already executes from a + // non-basic environment. Execute the override directly. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + HandleScope scope(env); + PostFinalizeCallback(env, data, static_cast(nullptr)); +#endif + } + // If the instance does _not_ override the (extended) finalizer, delete the + // `T* instance` immediately. + else { + delete instance; + } +} + +template +inline void ObjectWrap::PostFinalizeCallback(napi_env env, + void* data, + void* /*hint*/) { + T* instance = static_cast(data); + instance->Finalize(Napi::Env(env)); + delete instance; +} + +template +template ::StaticSetterCallback method> +inline napi_value ObjectWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback(env, [&] { + const CallbackInfo cbInfo(env, info); + // MSVC requires to copy 'method' function pointer to a local variable + // before invoking it. + auto m = method; + m(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// HandleScope class +//////////////////////////////////////////////////////////////////////////////// + +inline HandleScope::HandleScope(napi_env env, napi_handle_scope scope) + : _env(env), _scope(scope) {} + +inline HandleScope::HandleScope(Napi::Env env) : _env(env) { + napi_status status = napi_open_handle_scope(_env, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline HandleScope::~HandleScope() { + napi_status status = napi_close_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED( + status, "HandleScope::~HandleScope", "napi_close_handle_scope"); +} + +inline HandleScope::operator napi_handle_scope() const { + return _scope; +} + +inline Napi::Env HandleScope::Env() const { + return Napi::Env(_env); +} + +//////////////////////////////////////////////////////////////////////////////// +// EscapableHandleScope class +//////////////////////////////////////////////////////////////////////////////// + +inline EscapableHandleScope::EscapableHandleScope( + napi_env env, napi_escapable_handle_scope scope) + : _env(env), _scope(scope) {} + +inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { + napi_status status = napi_open_escapable_handle_scope(_env, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline EscapableHandleScope::~EscapableHandleScope() { + napi_status status = napi_close_escapable_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED(status, + "EscapableHandleScope::~EscapableHandleScope", + "napi_close_escapable_handle_scope"); +} + +inline EscapableHandleScope::operator napi_escapable_handle_scope() const { + return _scope; +} + +inline Napi::Env EscapableHandleScope::Env() const { + return Napi::Env(_env); +} + +inline Value EscapableHandleScope::Escape(napi_value escapee) { + napi_value result; + napi_status status = napi_escape_handle(_env, _scope, escapee, &result); + NAPI_THROW_IF_FAILED(_env, status, Value()); + return Value(_env, result); +} + +#if (NAPI_VERSION > 2) +//////////////////////////////////////////////////////////////////////////////// +// CallbackScope class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackScope::CallbackScope(napi_env env, napi_callback_scope scope) + : _env(env), _scope(scope) {} + +inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) + : _env(env) { + napi_status status = + napi_open_callback_scope(_env, Object::New(env), context, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline CallbackScope::~CallbackScope() { + napi_status status = napi_close_callback_scope(_env, _scope); + NAPI_FATAL_IF_FAILED( + status, "CallbackScope::~CallbackScope", "napi_close_callback_scope"); +} + +inline CallbackScope::operator napi_callback_scope() const { + return _scope; +} + +inline Napi::Env CallbackScope::Env() const { + return Napi::Env(_env); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// AsyncContext class +//////////////////////////////////////////////////////////////////////////////// + +inline AsyncContext::AsyncContext(napi_env env, const char* resource_name) + : AsyncContext(env, resource_name, Object::New(env)) {} + +inline AsyncContext::AsyncContext(napi_env env, + const char* resource_name, + const Object& resource) + : _env(env), _context(nullptr) { + napi_value resource_id; + napi_status status = napi_create_string_utf8( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_async_init(_env, resource, resource_id, &_context); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncContext::~AsyncContext() { + if (_context != nullptr) { + napi_async_destroy(_env, _context); + _context = nullptr; + } +} + +inline AsyncContext::AsyncContext(AsyncContext&& other) { + _env = other._env; + other._env = nullptr; + _context = other._context; + other._context = nullptr; +} + +inline AsyncContext& AsyncContext::operator=(AsyncContext&& other) { + _env = other._env; + other._env = nullptr; + _context = other._context; + other._context = nullptr; + return *this; +} + +inline AsyncContext::operator napi_async_context() const { + return _context; +} + +inline Napi::Env AsyncContext::Env() const { + return Napi::Env(_env); +} + +//////////////////////////////////////////////////////////////////////////////// +// AsyncWorker class +//////////////////////////////////////////////////////////////////////////////// + +#if NAPI_HAS_THREADS + +inline AsyncWorker::AsyncWorker(const Function& callback) + : AsyncWorker(callback, "generic") {} + +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name) + : AsyncWorker(callback, resource_name, Object::New(callback.Env())) {} + +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback) + : AsyncWorker(receiver, callback, "generic") {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : _env(callback.Env()), + _receiver(Napi::Persistent(receiver)), + _callback(Napi::Persistent(callback)), + _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncWorker::AsyncWorker(Napi::Env env) : AsyncWorker(env, "generic") {} + +inline AsyncWorker::AsyncWorker(Napi::Env env, const char* resource_name) + : AsyncWorker(env, resource_name, Object::New(env)) {} + +inline AsyncWorker::AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : _env(env), _receiver(), _callback(), _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncWorker::~AsyncWorker() { + if (_work != nullptr) { + napi_delete_async_work(_env, _work); + _work = nullptr; + } +} + +inline void AsyncWorker::Destroy() { + delete this; +} + +inline AsyncWorker::operator napi_async_work() const { + return _work; +} + +inline Napi::Env AsyncWorker::Env() const { + return Napi::Env(_env); +} + +inline void AsyncWorker::Queue() { + napi_status status = napi_queue_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void AsyncWorker::Cancel() { + napi_status status = napi_cancel_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline ObjectReference& AsyncWorker::Receiver() { + return _receiver; +} + +inline FunctionReference& AsyncWorker::Callback() { + return _callback; +} + +inline void AsyncWorker::SuppressDestruct() { + _suppress_destruct = true; +} + +inline void AsyncWorker::OnOK() { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), GetResult(_callback.Env())); + } +} + +inline void AsyncWorker::OnError(const Error& e) { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), + std::initializer_list{e.Value()}); + } +} + +inline void AsyncWorker::SetError(const std::string& error) { + _error = error; +} + +inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { + return {}; +} +// The OnAsyncWorkExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnExecute(env); +} +// The OnExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + Execute(); + } catch (const std::exception& e) { + SetError(e.what()); + } +#else // NODE_ADDON_API_CPP_EXCEPTIONS + Execute(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + +inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnWorkComplete(env, status); +} +inline void AsyncWorker::OnWorkComplete(Napi::Env env, napi_status status) { + if (status != napi_cancelled) { + HandleScope scope(_env); + details::WrapCallback(env, [&] { + if (_error.size() == 0) { + OnOK(); + } else { + OnError(Error::New(_env, _error)); + } + return nullptr; + }); + } + if (!_suppress_destruct) { + Destroy(); + } +} + +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +//////////////////////////////////////////////////////////////////////////////// +// TypedThreadSafeFunction class +//////////////////////////////////////////////////////////////////////////////// + +// Starting with NAPI 5, the JavaScript function `func` parameter of +// `napi_create_threadsafe_function` is optional. +#if NAPI_VERSION > 4 +// static, with Callback [missing] Resource [missing] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [missing] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} +#endif + +// static, with Callback [passed] Resource [missing] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [passed] Resource [passed] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + callback, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [passed] Resource [missing] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with: Callback [passed] Resource [passed] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + CallbackType callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = napi_create_threadsafe_function( + env, + details::DefaultCallbackWrapper< + CallbackType, + TypedThreadSafeFunction>(env, + callback), + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +template +inline TypedThreadSafeFunction:: + TypedThreadSafeFunction() + : _tsfn() {} + +template +inline TypedThreadSafeFunction:: + TypedThreadSafeFunction(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} + +template +inline TypedThreadSafeFunction:: +operator napi_threadsafe_function() const { + return _tsfn; +} + +template +inline napi_status +TypedThreadSafeFunction::BlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + +template +inline napi_status +TypedThreadSafeFunction::NonBlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + +template +inline void TypedThreadSafeFunction::Ref( + napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline void TypedThreadSafeFunction::Unref( + napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline napi_status +TypedThreadSafeFunction::Acquire() const { + return napi_acquire_threadsafe_function(_tsfn); +} + +template +inline napi_status +TypedThreadSafeFunction::Release() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); +} + +template +inline napi_status +TypedThreadSafeFunction::Abort() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); +} + +template +inline ContextType* +TypedThreadSafeFunction::GetContext() const { + void* context; + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, + "TypedThreadSafeFunction::GetContext", + "napi_get_threadsafe_function_context"); + return static_cast(context); +} + +// static +template +void TypedThreadSafeFunction::CallJsInternal( + napi_env env, napi_value jsCallback, void* context, void* data) { + details::CallJsWrapper( + env, jsCallback, context, data); +} + +#if NAPI_VERSION == 4 +// static +template +Napi::Function +TypedThreadSafeFunction::EmptyFunctionFactory( + Napi::Env env) { + return Napi::Function::New(env, [](const CallbackInfo& cb) {}); +} + +// static +template +Napi::Function +TypedThreadSafeFunction::FunctionOrEmpty( + Napi::Env env, Napi::Function& callback) { + if (callback.IsEmpty()) { + return EmptyFunctionFactory(env); + } + return callback; +} + +#else +// static +template +std::nullptr_t +TypedThreadSafeFunction::EmptyFunctionFactory( + Napi::Env /*env*/) { + return nullptr; +} + +// static +template +Napi::Function +TypedThreadSafeFunction::FunctionOrEmpty( + Napi::Env /*env*/, Napi::Function& callback) { + return callback; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// ThreadSafeFunction class +//////////////////////////////////////////////////////////////////////////////// + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New( + env, callback, Object(), resourceName, maxQueueSize, initialThreadCount); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback, + data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + [](Env, ContextType*) {} /* empty finalizer */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::Wrapper); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeWrapperWithData); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::FinalizeWrapperWithContext); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext); +} + +inline ThreadSafeFunction::ThreadSafeFunction() : _tsfn() {} + +inline ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} + +inline ThreadSafeFunction::operator napi_threadsafe_function() const { + return _tsfn; +} + +inline napi_status ThreadSafeFunction::BlockingCall() const { + return CallInternal(nullptr, napi_tsfn_blocking); +} + +template <> +inline napi_status ThreadSafeFunction::BlockingCall(void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall(Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall(DataType* data, + Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking); +} + +inline napi_status ThreadSafeFunction::NonBlockingCall() const { + return CallInternal(nullptr, napi_tsfn_nonblocking); +} + +template <> +inline napi_status ThreadSafeFunction::NonBlockingCall(void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + DataType* data, Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking); +} + +inline void ThreadSafeFunction::Ref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +inline void ThreadSafeFunction::Unref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +inline napi_status ThreadSafeFunction::Acquire() const { + return napi_acquire_threadsafe_function(_tsfn); +} + +inline napi_status ThreadSafeFunction::Release() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); +} + +inline napi_status ThreadSafeFunction::Abort() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); +} + +inline ThreadSafeFunction::ConvertibleContext ThreadSafeFunction::GetContext() + const { + void* context; + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, + "ThreadSafeFunction::GetContext", + "napi_get_threadsafe_function_context"); + return ConvertibleContext({context}); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper) { + static_assert(details::can_make_string::value || + std::is_convertible::value, + "Resource name should be convertible to the string type"); + + ThreadSafeFunction tsfn; + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = + napi_create_threadsafe_function(env, + callback, + resource, + Value::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + wrapper, + context, + CallJS, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction()); + } + + return tsfn; +} + +inline napi_status ThreadSafeFunction::CallInternal( + CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const { + napi_status status = + napi_call_threadsafe_function(_tsfn, callbackWrapper, mode); + if (status != napi_ok && callbackWrapper != nullptr) { + delete callbackWrapper; + } + + return status; +} + +// static +inline void ThreadSafeFunction::CallJS(napi_env env, + napi_value jsCallback, + void* /* context */, + void* data) { + if (env == nullptr && jsCallback == nullptr) { + return; + } + + details::WrapVoidCallback(env, [&]() { + if (data != nullptr) { + auto* callbackWrapper = static_cast(data); + (*callbackWrapper)(env, Function(env, jsCallback)); + delete callbackWrapper; + } else if (jsCallback != nullptr) { + Function(env, jsCallback).Call({}); + } + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker Base class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(receiver, callback, resource_name, resource) { + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(callback.Env(), + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(env, resource_name, resource) { + // TODO: Once the changes to make the callback optional for threadsafe + // functions are available on all versions we can remove the dummy Function + // here. + Function callback; + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(env, + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} +#endif + +template +inline AsyncProgressWorkerBase::~AsyncProgressWorkerBase() { + // Abort pending tsfn call. + // Don't send progress events after we've already completed. + // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release + // duplicated. + _tsfn.Abort(); +} + +template +inline void AsyncProgressWorkerBase::OnAsyncWorkProgress( + Napi::Env /* env */, Napi::Function /* jsCallback */, void* data) { + ThreadSafeData* tsd = static_cast(data); + tsd->asyncprogressworker()->OnWorkProgress(tsd->data()); + delete tsd; +} + +template +inline napi_status AsyncProgressWorkerBase::NonBlockingCall( + DataType* data) { + auto tsd = new AsyncProgressWorkerBase::ThreadSafeData(this, data); + auto ret = _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); + if (ret != napi_ok) { + delete tsd; + } + return ret; +} + +template +inline void AsyncProgressWorkerBase::OnWorkComplete( + Napi::Env /* env */, napi_status status) { + _work_completed = true; + _complete_status = status; + _tsfn.Release(); +} + +template +inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize( + Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { + if (context->_work_completed) { + context->AsyncWorker::OnWorkComplete(env, context->_complete_status); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) + : AsyncProgressWorker(callback, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name) + : AsyncProgressWorker( + callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback) + : AsyncProgressWorker(receiver, callback, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncProgressWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0), + _signaled(false) {} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) + : AsyncProgressWorker(env, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name) + : AsyncProgressWorker(env, resource_name, Object::New(env)) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase(env, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0) {} +#endif + +template +inline AsyncProgressWorker::~AsyncProgressWorker() { + { + std::lock_guard lock(this->_mutex); + _asyncdata = nullptr; + _asyncsize = 0; + } +} + +template +inline void AsyncProgressWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressWorker::OnWorkProgress(void*) { + T* data; + size_t size; + bool signaled; + { + std::lock_guard lock(this->_mutex); + data = this->_asyncdata; + size = this->_asyncsize; + signaled = this->_signaled; + this->_asyncdata = nullptr; + this->_asyncsize = 0; + this->_signaled = false; + } + + /** + * The callback of ThreadSafeFunction is not been invoked immediately on the + * callback of uv_async_t (uv io poll), rather the callback of TSFN is + * invoked on the right next uv idle callback. There are chances that during + * the deferring the signal of uv_async_t is been sent again, i.e. potential + * not coalesced two calls of the TSFN callback. + */ + if (data == nullptr && !signaled) { + return; + } + + this->OnProgress(data, size); + delete[] data; +} + +template +inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + T* old_data; + { + std::lock_guard lock(this->_mutex); + old_data = _asyncdata; + _asyncdata = new_data; + _asyncsize = count; + _signaled = false; + } + this->NonBlockingCall(nullptr); + + delete[] old_data; +} + +template +inline void AsyncProgressWorker::Signal() { + { + std::lock_guard lock(this->_mutex); + _signaled = true; + } + this->NonBlockingCall(static_cast(nullptr)); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Signal() const { + this->_worker->Signal(); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Send( + const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Queue Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback) + : AsyncProgressQueueWorker(callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name, const Object& resource) + : AsyncProgressQueueWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback) + : AsyncProgressQueueWorker(receiver, callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>( + receiver, + callback, + resource_name, + resource, + /** unlimited queue size */ 0) {} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env) + : AsyncProgressQueueWorker(env, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name) + : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name, const Object& resource) + : AsyncProgressWorkerBase>( + env, resource_name, resource, /** unlimited queue size */ 0) {} +#endif + +template +inline void AsyncProgressQueueWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressQueueWorker::OnWorkProgress( + std::pair* datapair) { + if (datapair == nullptr) { + return; + } + + T* data = datapair->first; + size_t size = datapair->second; + + this->OnProgress(data, size); + delete datapair; + delete[] data; +} + +template +inline void AsyncProgressQueueWorker::SendProgress_(const T* data, + size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + auto pair = new std::pair(new_data, count); + this->NonBlockingCall(pair); +} + +template +inline void AsyncProgressQueueWorker::Signal() const { + this->SendProgress_(static_cast(nullptr), 0); +} + +template +inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, + napi_status status) { + // Draining queued items in TSFN. + AsyncProgressWorkerBase>::OnWorkComplete(env, status); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Signal() const { + _worker->SendProgress_(static_cast(nullptr), 0); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Send( + const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +//////////////////////////////////////////////////////////////////////////////// +// Memory Management class +//////////////////////////////////////////////////////////////////////////////// + +inline int64_t MemoryManagement::AdjustExternalMemory(BasicEnv env, + int64_t change_in_bytes) { + int64_t result; + napi_status status = + napi_adjust_external_memory(env, change_in_bytes, &result); + NAPI_FATAL_IF_FAILED(status, + "MemoryManagement::AdjustExternalMemory", + "napi_adjust_external_memory"); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Version Management class +//////////////////////////////////////////////////////////////////////////////// + +inline uint32_t VersionManagement::GetNapiVersion(BasicEnv env) { + uint32_t result; + napi_status status = napi_get_version(env, &result); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNapiVersion", "napi_get_version"); + return result; +} + +inline const napi_node_version* VersionManagement::GetNodeVersion( + BasicEnv env) { + const napi_node_version* result; + napi_status status = napi_get_node_version(env, &result); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNodeVersion", "napi_get_node_version"); + return result; +} + +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// Addon class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object Addon::Init(Env env, Object exports) { + T* addon = new T(env, exports); + env.SetInstanceData(addon); + return addon->entry_point_; +} + +template +inline T* Addon::Unwrap(Object wrapper) { + return wrapper.Env().GetInstanceData(); +} + +template +inline void Addon::DefineAddon( + Object exports, const std::initializer_list& props) { + DefineProperties(exports, props); + entry_point_ = exports; +} + +template +inline Napi::Object Addon::DefineProperties( + Object object, const std::initializer_list& props) { + const napi_property_descriptor* properties = + reinterpret_cast(props.begin()); + size_t size = props.size(); + napi_status status = + napi_define_properties(object.Env(), object, size, properties); + NAPI_THROW_IF_FAILED(object.Env(), status, object); + for (size_t idx = 0; idx < size; idx++) + T::AttachPropData(object.Env(), object, &properties[idx]); + return object; +} +#endif // NAPI_VERSION > 5 + +#if NAPI_VERSION > 2 +template +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook, Arg* arg) { + return CleanupHook(*this, hook, arg); +} + +template +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook) { + return CleanupHook(*this, hook); +} + +template +Env::CleanupHook::CleanupHook() { + data = nullptr; +} + +template +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, Hook hook) + : wrapper(Env::CleanupHook::Wrapper) { + data = new CleanupData{std::move(hook), nullptr}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, + Hook hook, + Arg* arg) + : wrapper(Env::CleanupHook::WrapperWithArg) { + data = new CleanupData{std::move(hook), arg}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +bool Env::CleanupHook::Remove(BasicEnv env) { + napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data); + delete data; + data = nullptr; + return status == napi_ok; +} + +template +bool Env::CleanupHook::IsEmpty() const { + return data == nullptr; +} +#endif // NAPI_VERSION > 2 + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const { + using T = void*; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithoutData, + static_cast(nullptr), + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGC, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithHint, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi + +#endif // SRC_NAPI_INL_H_ diff --git a/c++/napi.h b/c++/napi.h new file mode 100644 index 0000000..3926c1c --- /dev/null +++ b/c++/napi.h @@ -0,0 +1,3290 @@ +#ifndef SRC_NAPI_H_ +#define SRC_NAPI_H_ + +#ifndef NAPI_HAS_THREADS +#if !defined(__wasm__) || (defined(__EMSCRIPTEN_PTHREADS__) || \ + (defined(__wasi__) && defined(_REENTRANT))) +#define NAPI_HAS_THREADS 1 +#else +#define NAPI_HAS_THREADS 0 +#endif +#endif + +#include +#include +#include +#include +#if NAPI_HAS_THREADS +#include +#endif // NAPI_HAS_THREADS +#include +#include + +// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known +// good version) +#if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210 +#define NAPI_HAS_CONSTEXPR 1 +#endif + +// VS2013 does not support char16_t literal strings, so we'll work around it +// using wchar_t strings and casting them. This is safe as long as the character +// sizes are the same. +#if defined(_MSC_VER) && _MSC_VER <= 1800 +static_assert(sizeof(char16_t) == sizeof(wchar_t), + "Size mismatch between char16_t and wchar_t"); +#define NAPI_WIDE_TEXT(x) reinterpret_cast(L##x) +#else +#define NAPI_WIDE_TEXT(x) u##x +#endif + +// Backwards-compatibility to handle the rename of this macro definition, in +// case they are used within userland code. +#ifdef NAPI_CPP_EXCEPTIONS +#define NODE_ADDON_API_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && !defined(NAPI_CPP_EXCEPTIONS) +#define NAPI_CPP_EXCEPTIONS +#endif +#ifdef NAPI_DISABLE_CPP_EXCEPTIONS +#define NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) && \ + !defined(NAPI_DISABLE_CPP_EXCEPTIONS) +#define NAPI_DISABLE_CPP_EXCEPTIONS +#endif + +// If C++ exceptions are not explicitly enabled or disabled, enable them +// if exceptions were enabled in the compiler settings. +#if !defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + !defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) +#if defined(_CPPUNWIND) || defined(__EXCEPTIONS) +#define NODE_ADDON_API_CPP_EXCEPTIONS +#else +#error Exception support not detected. \ + Define either NODE_ADDON_API_CPP_EXCEPTIONS or NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS. +#endif +#endif + +// If C++ NODE_ADDON_API_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE +// should not be set +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + defined(NODE_ADDON_API_ENABLE_MAYBE) +#error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ + NODE_ADDON_API_CPP_EXCEPTIONS is defined. +#endif + +#ifdef _NOEXCEPT +#define NAPI_NOEXCEPT _NOEXCEPT +#else +#define NAPI_NOEXCEPT noexcept +#endif + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + +// When C++ exceptions are enabled, Errors are thrown directly. There is no need +// to return anything after the throw statements. The variadic parameter is an +// optional return value that is ignored. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) throw e +#define NAPI_THROW_VOID(e) throw e + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) throw Napi::Error::New(env); + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) throw Napi::Error::New(env); + +#else // NODE_ADDON_API_CPP_EXCEPTIONS + +// When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, +// which are pending until the callback returns to JS. The variadic parameter +// is an optional return value; usually it is an empty result. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } while (0) + +#define NAPI_THROW_VOID(e) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return; \ + } while (0) + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return; \ + } + +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#ifdef NODE_ADDON_API_ENABLE_MAYBE +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return Napi::Just(result); +#else +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, type()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return result; +#endif + +#define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; +#define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; + +#define NAPI_DISALLOW_ASSIGN_COPY(CLASS) \ + NAPI_DISALLOW_ASSIGN(CLASS) \ + NAPI_DISALLOW_COPY(CLASS) + +#define NAPI_CHECK(condition, location, message) \ + do { \ + if (!(condition)) { \ + Napi::Error::Fatal((location), (message)); \ + } \ + } while (0) + +// Internal check helper. Be careful that the formatted message length should be +// max 255 size and null terminated. +#define NAPI_INTERNAL_CHECK(expr, location, ...) \ + do { \ + if (!(expr)) { \ + std::string msg = Napi::details::StringFormat(__VA_ARGS__); \ + Napi::Error::Fatal(location, msg.c_str()); \ + } \ + } while (0) + +#define NAPI_INTERNAL_CHECK_EQ(actual, expected, value_format, location) \ + do { \ + auto actual_value = (actual); \ + NAPI_INTERNAL_CHECK(actual_value == (expected), \ + location, \ + "Expected " #actual " to be equal to " #expected \ + ", but got " value_format ".", \ + actual_value); \ + } while (0) + +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + NAPI_CHECK((status) == napi_ok, location, message) + +//////////////////////////////////////////////////////////////////////////////// +/// Node-API C++ Wrapper Classes +/// +/// These classes wrap the "Node-API" ABI-stable C APIs for Node.js, providing a +/// C++ object model and C++ exception-handling semantics with low overhead. +/// The wrappers are all header-only so that they do not affect the ABI. +//////////////////////////////////////////////////////////////////////////////// +namespace Napi { + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +// NAPI_CPP_CUSTOM_NAMESPACE can be #define'd per-addon to avoid symbol +// conflicts between different instances of node-addon-api + +// First dummy definition of the namespace to make sure that Napi::(name) still +// refers to the right things inside this file. +namespace NAPI_CPP_CUSTOM_NAMESPACE {} +using namespace NAPI_CPP_CUSTOM_NAMESPACE; + +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Forward declarations +class Env; +class Value; +class Boolean; +class Number; +#if NAPI_VERSION > 5 +class BigInt; +#endif // NAPI_VERSION > 5 +#if (NAPI_VERSION > 4) +class Date; +#endif +class String; +class Object; +class Array; +class ArrayBuffer; +class Function; +class Error; +class PropertyDescriptor; +class CallbackInfo; +class TypedArray; +template +class TypedArrayOf; + +using Int8Array = + TypedArrayOf; ///< Typed-array of signed 8-bit integers +using Uint8Array = + TypedArrayOf; ///< Typed-array of unsigned 8-bit integers +using Int16Array = + TypedArrayOf; ///< Typed-array of signed 16-bit integers +using Uint16Array = + TypedArrayOf; ///< Typed-array of unsigned 16-bit integers +using Int32Array = + TypedArrayOf; ///< Typed-array of signed 32-bit integers +using Uint32Array = + TypedArrayOf; ///< Typed-array of unsigned 32-bit integers +using Float32Array = + TypedArrayOf; ///< Typed-array of 32-bit floating-point values +using Float64Array = + TypedArrayOf; ///< Typed-array of 64-bit floating-point values +#if NAPI_VERSION > 5 +using BigInt64Array = + TypedArrayOf; ///< Typed array of signed 64-bit integers +using BigUint64Array = + TypedArrayOf; ///< Typed array of unsigned 64-bit integers +#endif // NAPI_VERSION > 5 + +/// Defines the signature of a Node-API C++ module's registration callback +/// (init) function. +using ModuleRegisterCallback = Object (*)(Env env, Object exports); + +class MemoryManagement; + +/// A simple Maybe type, representing an object which may or may not have a +/// value. +/// +/// If an API method returns a Maybe<>, the API method can potentially fail +/// either because an exception is thrown, or because an exception is pending, +/// e.g. because a previous API call threw an exception that hasn't been +/// caught yet. In that case, a "Nothing" value is returned. +template +class Maybe { + public: + bool IsNothing() const; + bool IsJust() const; + + /// Short-hand for Unwrap(), which doesn't return a value. Could be used + /// where the actual value of the Maybe is not needed like Object::Set. + /// If this Maybe is nothing (empty), node-addon-api will crash the + /// process. + void Check() const; + + /// Return the value of type T contained in the Maybe. If this Maybe is + /// nothing (empty), node-addon-api will crash the process. + T Unwrap() const; + + /// Return the value of type T contained in the Maybe, or using a default + /// value if this Maybe is nothing (empty). + T UnwrapOr(const T& default_value) const; + + /// Converts this Maybe to a value of type T in the out. If this Maybe is + /// nothing (empty), `false` is returned and `out` is left untouched. + bool UnwrapTo(T* out) const; + + bool operator==(const Maybe& other) const; + bool operator!=(const Maybe& other) const; + + private: + Maybe(); + explicit Maybe(const T& t); + + bool _has_value; + T _value; + + template + friend Maybe Nothing(); + template + friend Maybe Just(const U& u); +}; + +template +inline Maybe Nothing(); + +template +inline Maybe Just(const T& t); + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +template +using MaybeOrValue = Maybe; +#else +template +using MaybeOrValue = T; +#endif + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +using node_addon_api_basic_env = node_api_nogc_env; +using node_addon_api_basic_finalize = node_api_nogc_finalize; +#else +using node_addon_api_basic_env = napi_env; +using node_addon_api_basic_finalize = napi_finalize; +#endif + +/// Environment for Node-API values and operations. +/// +/// All Node-API values and operations must be associated with an environment. +/// An environment instance is always provided to callback functions; that +/// environment must then be used for any creation of Node-API values or other +/// Node-API operations within the callback. (Many methods infer the +/// environment from the `this` instance that the method is called on.) +/// +/// Multiple environments may co-exist in a single process or a thread. +/// +/// In the V8 JavaScript engine, a Node-API environment approximately +/// corresponds to an Isolate. +class BasicEnv { + private: + node_addon_api_basic_env _env; +#if NAPI_VERSION > 5 + template + static void DefaultFini(Env, T* data); + template + static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); +#endif // NAPI_VERSION > 5 + public: + BasicEnv(node_addon_api_basic_env env); + + operator node_addon_api_basic_env() const; + + // Without these operator overloads, the error: + // + // Use of overloaded operator '==' is ambiguous (with operand types + // 'Napi::Env' and 'Napi::Env') + // + // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr + bool operator==(const BasicEnv& other) const { + return _env == other._env; + }; + bool operator==(std::nullptr_t /*other*/) const { + return _env == nullptr; + }; + +#if NAPI_VERSION > 2 + template + class CleanupHook; + + template + CleanupHook AddCleanupHook(Hook hook); + + template + CleanupHook AddCleanupHook(Hook hook, Arg* arg); +#endif // NAPI_VERSION > 2 + +#if NAPI_VERSION > 5 + template + T* GetInstanceData() const; + + template + using Finalizer = void (*)(Env, T*); + template fini = BasicEnv::DefaultFini> + void SetInstanceData(T* data) const; + + template + using FinalizerWithHint = void (*)(Env, DataType*, HintType*); + template fini = + BasicEnv::DefaultFiniWithHint> + void SetInstanceData(DataType* data, HintType* hint) const; +#endif // NAPI_VERSION > 5 + +#if NAPI_VERSION > 2 + template + class CleanupHook { + public: + CleanupHook(); + CleanupHook(BasicEnv env, Hook hook, Arg* arg); + CleanupHook(BasicEnv env, Hook hook); + bool Remove(BasicEnv env); + bool IsEmpty() const; + + private: + static inline void Wrapper(void* data) NAPI_NOEXCEPT; + static inline void WrapperWithArg(void* data) NAPI_NOEXCEPT; + + void (*wrapper)(void* arg); + struct CleanupData { + Hook hook; + Arg* arg; + } * data; + }; +#endif // NAPI_VERSION > 2 + +#if NAPI_VERSION > 8 + const char* GetModuleFileName() const; +#endif // NAPI_VERSION > 8 + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template + inline void PostFinalizer(FinalizerType finalizeCallback) const; + + template + inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; + + template + inline void PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const; +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + + friend class Env; +}; + +class Env : public BasicEnv { + public: + Env(napi_env env); + + operator napi_env() const; + + Object Global() const; + Value Undefined() const; + Value Null() const; + + bool IsExceptionPending() const; + Error GetAndClearPendingException() const; + + MaybeOrValue RunScript(const char* utf8script) const; + MaybeOrValue RunScript(const std::string& utf8script) const; + MaybeOrValue RunScript(String script) const; +}; + +/// A JavaScript value of unknown type. +/// +/// For type-specific operations, convert to one of the Value subclasses using a +/// `To*` or `As()` method. The `To*` methods do type coercion; the `As()` +/// method does not. +/// +/// Napi::Value value = ... +/// if (!value.IsString()) throw Napi::TypeError::New(env, "Invalid +/// arg..."); Napi::String str = value.As(); // Cast to a +/// string value +/// +/// Napi::Value anotherValue = ... +/// bool isTruthy = anotherValue.ToBoolean(); // Coerce to a boolean value +class Value { + public: + Value(); ///< Creates a new _empty_ Value instance. + Value(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Creates a JS value from a C++ primitive. + /// + /// `value` may be any of: + /// - bool + /// - Any integer type + /// - Any floating point type + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + /// - napi::Value + /// - napi_value + template + static Value From(napi_env env, const T& value); + + static void CheckCast(napi_env env, napi_value value); + + /// Converts to a Node-API value primitive. + /// + /// If the instance is _empty_, this returns `nullptr`. + operator napi_value() const; + + /// Tests if this value strictly equals another value. + bool operator==(const Value& other) const; + + /// Tests if this value does not strictly equal another value. + bool operator!=(const Value& other) const; + + /// Tests if this value strictly equals another value. + bool StrictEquals(const Value& other) const; + + /// Gets the environment the value is associated with. + Napi::Env Env() const; + + /// Checks if the value is empty (uninitialized). + /// + /// An empty value is invalid, and most attempts to perform an operation on an + /// empty value will result in an exception. Note an empty value is distinct + /// from JavaScript `null` or `undefined`, which are valid values. + /// + /// When C++ exceptions are disabled at compile time, a method with a `Value` + /// return type may return an empty value to indicate a pending exception. So + /// when not using C++ exceptions, callers should check whether the value is + /// empty before attempting to use it. + bool IsEmpty() const; + + napi_valuetype Type() const; ///< Gets the type of the value. + + bool IsUndefined() + const; ///< Tests if a value is an undefined JavaScript value. + bool IsNull() const; ///< Tests if a value is a null JavaScript value. + bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. + bool IsNumber() const; ///< Tests if a value is a JavaScript number. +#if NAPI_VERSION > 5 + bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. +#endif // NAPI_VERSION > 5 +#if (NAPI_VERSION > 4) + bool IsDate() const; ///< Tests if a value is a JavaScript date. +#endif + bool IsString() const; ///< Tests if a value is a JavaScript string. + bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. + bool IsArray() const; ///< Tests if a value is a JavaScript array. + bool IsArrayBuffer() + const; ///< Tests if a value is a JavaScript array buffer. + bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array. + bool IsObject() const; ///< Tests if a value is a JavaScript object. + bool IsFunction() const; ///< Tests if a value is a JavaScript function. + bool IsPromise() const; ///< Tests if a value is a JavaScript promise. + bool IsDataView() const; ///< Tests if a value is a JavaScript data view. + bool IsBuffer() const; ///< Tests if a value is a Node buffer. + bool IsExternal() const; ///< Tests if a value is a pointer to external data. + + /// Casts to another type of `Napi::Value`, when the actual type is known or + /// assumed. + /// + /// This conversion does NOT coerce the type. Calling any methods + /// inappropriate for the actual value type will throw `Napi::Error`. + /// + /// If `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined, this method + /// asserts that the actual type is the expected type. + template + T As() const; + + // Unsafe Value::As(), should be avoided. + template + T UnsafeAs() const; + + MaybeOrValue ToBoolean() + const; ///< Coerces a value to a JavaScript boolean. + MaybeOrValue ToNumber() + const; ///< Coerces a value to a JavaScript number. + MaybeOrValue ToString() + const; ///< Coerces a value to a JavaScript string. + MaybeOrValue ToObject() + const; ///< Coerces a value to a JavaScript object. + + protected: + /// !cond INTERNAL + napi_env _env; + napi_value _value; + /// !endcond +}; + +/// A JavaScript boolean value. +class Boolean : public Value { + public: + static Boolean New(napi_env env, ///< Node-API environment + bool value ///< Boolean value + ); + + static void CheckCast(napi_env env, napi_value value); + + Boolean(); ///< Creates a new _empty_ Boolean instance. + Boolean(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator bool() const; ///< Converts a Boolean value to a boolean primitive. + bool Value() const; ///< Converts a Boolean value to a boolean primitive. +}; + +/// A JavaScript number value. +class Number : public Value { + public: + static Number New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + static void CheckCast(napi_env env, napi_value value); + + Number(); ///< Creates a new _empty_ Number instance. + Number(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator int32_t() + const; ///< Converts a Number value to a 32-bit signed integer value. + operator uint32_t() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + operator int64_t() + const; ///< Converts a Number value to a 64-bit signed integer value. + operator float() + const; ///< Converts a Number value to a 32-bit floating-point value. + operator double() + const; ///< Converts a Number value to a 64-bit floating-point value. + + int32_t Int32Value() + const; ///< Converts a Number value to a 32-bit signed integer value. + uint32_t Uint32Value() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + int64_t Int64Value() + const; ///< Converts a Number value to a 64-bit signed integer value. + float FloatValue() + const; ///< Converts a Number value to a 32-bit floating-point value. + double DoubleValue() + const; ///< Converts a Number value to a 64-bit floating-point value. +}; + +#if NAPI_VERSION > 5 +/// A JavaScript bigint value. +class BigInt : public Value { + public: + static BigInt New(napi_env env, ///< Node-API environment + int64_t value ///< Number value + ); + static BigInt New(napi_env env, ///< Node-API environment + uint64_t value ///< Number value + ); + + /// Creates a new BigInt object using a specified sign bit and a + /// specified list of digits/words. + /// The resulting number is calculated as: + /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) + static BigInt New(napi_env env, ///< Node-API environment + int sign_bit, ///< Sign bit. 1 if negative. + size_t word_count, ///< Number of words in array + const uint64_t* words ///< Array of words + ); + + static void CheckCast(napi_env env, napi_value value); + + BigInt(); ///< Creates a new _empty_ BigInt instance. + BigInt(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + int64_t Int64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit signed integer value. + uint64_t Uint64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit unsigned integer value. + + size_t WordCount() const; ///< The number of 64-bit words needed to store + ///< the result of ToWords(). + + /// Writes the contents of this BigInt to a specified memory location. + /// `sign_bit` must be provided and will be set to 1 if this BigInt is + /// negative. + /// `*word_count` has to be initialized to the length of the `words` array. + /// Upon return, it will be set to the actual number of words that would + /// be needed to store this BigInt (i.e. the return value of `WordCount()`). + void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); +}; +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +/// A JavaScript date value. +class Date : public Value { + public: + /// Creates a new Date value from a double primitive. + static Date New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + static void CheckCast(napi_env env, napi_value value); + + Date(); ///< Creates a new _empty_ Date instance. + Date(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. + operator double() const; ///< Converts a Date value to double primitive + + double ValueOf() const; ///< Converts a Date value to a double primitive. +}; +#endif + +/// A JavaScript string or symbol value (that can be used as a property name). +class Name : public Value { + public: + static void CheckCast(napi_env env, napi_value value); + + Name(); ///< Creates a new _empty_ Name instance. + Name(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +/// A JavaScript string value. +class String : public Name { + public: + /// Creates a new String value from a UTF-8 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::string& value ///< UTF-8 encoded C++ string + ); + + /// Creates a new String value from a UTF-16 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::u16string& value ///< UTF-16 encoded C++ string + ); + + /// Creates a new String value from a UTF-8 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char* value ///< UTF-8 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-16 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value ///< UTF-16 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-8 encoded C string with specified + /// length. + static String New(napi_env env, ///< Node-API environment + const char* value, ///< UTF-8 encoded C string (not + ///< necessarily null-terminated) + size_t length ///< length of the string in bytes + ); + + /// Creates a new String value from a UTF-16 encoded C string with specified + /// length. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value, ///< UTF-16 encoded C string (not necessarily + ///< null-terminated) + size_t length ///< Length of the string in 2-byte code units + ); + + /// Creates a new String based on the original object's type. + /// + /// `value` may be any of: + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + template + static String From(napi_env env, const T& value); + + static void CheckCast(napi_env env, napi_value value); + + String(); ///< Creates a new _empty_ String instance. + String(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator std::string() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + operator std::u16string() + const; ///< Converts a String value to a UTF-16 encoded C++ string. + std::string Utf8Value() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + std::u16string Utf16Value() + const; ///< Converts a String value to a UTF-16 encoded C++ string. +}; + +/// A JavaScript symbol value. +class Symbol : public Name { + public: + /// Creates a new Symbol value with an optional description. + static Symbol New( + napi_env env, ///< Node-API environment + const char* description = + nullptr ///< Optional UTF-8 encoded null-terminated C string + /// describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + const std::string& + description ///< UTF-8 encoded C++ string describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New(napi_env env, ///< Node-API environment + String description ///< String value describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + napi_value description ///< String value describing the symbol + ); + + /// Get a public Symbol (e.g. Symbol.iterator). + static MaybeOrValue WellKnown(napi_env, const std::string& name); + + // Create a symbol in the global registry, UTF-8 Encoded cpp string + static MaybeOrValue For(napi_env env, const std::string& description); + + // Create a symbol in the global registry, C style string (null terminated) + static MaybeOrValue For(napi_env env, const char* description); + + // Create a symbol in the global registry, String value describing the symbol + static MaybeOrValue For(napi_env env, String description); + + // Create a symbol in the global registry, napi_value describing the symbol + static MaybeOrValue For(napi_env env, napi_value description); + + static void CheckCast(napi_env env, napi_value value); + + Symbol(); ///< Creates a new _empty_ Symbol instance. + Symbol(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +class TypeTaggable : public Value { + public: +#if NAPI_VERSION >= 8 + void TypeTag(const napi_type_tag* type_tag) const; + bool CheckTypeTag(const napi_type_tag* type_tag) const; +#endif // NAPI_VERSION >= 8 + protected: + TypeTaggable(); + TypeTaggable(napi_env env, napi_value value); +}; + +/// A JavaScript object value. +class Object : public TypeTaggable { + public: + /// Enables property and element assignments using indexing syntax. + /// + /// This is a convenient helper to get and set object properties. As + /// getting and setting object properties may throw with JavaScript + /// exceptions, it is notable that these operations may fail. + /// When NODE_ADDON_API_ENABLE_MAYBE is defined, the process will abort + /// on JavaScript exceptions. + /// + /// Example: + /// + /// Napi::Value propertyValue = object1['A']; + /// object2['A'] = propertyValue; + /// Napi::Value elementValue = array[0]; + /// array[1] = elementValue; + template + class PropertyLValue { + public: + /// Converts an L-value to a value. + operator Value() const; + + /// Assigns a value to the property. The type of value can be + /// anything supported by `Object::Set`. + template + PropertyLValue& operator=(ValueType value); + + private: + PropertyLValue() = delete; + PropertyLValue(Object object, Key key); + napi_env _env; + napi_value _object; + Key _key; + + friend class Napi::Object; + }; + + /// Creates a new Object value. + static Object New(napi_env env ///< Node-API environment + ); + + static void CheckCast(napi_env env, napi_value value); + + Object(); ///< Creates a new _empty_ Object instance. + Object(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Gets or sets a named property. + PropertyLValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ); + + /// Gets or sets a named property. + PropertyLValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[]( + uint32_t index /// Property / element index + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[](Value index /// Property / element index + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue operator[](uint32_t index ///< Property / element index + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(Value key ///< Property key + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(Value key ///< Property key + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets a property. + MaybeOrValue Get(napi_value key ///< Property key primitive + ) const; + + /// Gets a property. + MaybeOrValue Get(Value key ///< Property key + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Sets a property. + template + MaybeOrValue Set(napi_value key, ///< Property key primitive + const ValueType& value ///< Property value primitive + ) const; + + /// Sets a property. + template + MaybeOrValue Set(Value key, ///< Property key + const ValueType& value ///< Property value + ) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const char* utf8name, ///< UTF-8 encoded null-terminated property name + const ValueType& value) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const std::string& utf8name, ///< UTF-8 encoded property name + const ValueType& value ///< Property value primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(napi_value key ///< Property key primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(Value key ///< Property key + ) const; + + /// Delete property. + MaybeOrValue Delete( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Delete property. + MaybeOrValue Delete( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether an indexed property is present. + MaybeOrValue Has(uint32_t index ///< Property / element index + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue Get(uint32_t index ///< Property / element index + ) const; + + /// Sets an indexed property or array element. + template + MaybeOrValue Set(uint32_t index, ///< Property / element index + const ValueType& value ///< Property value primitive + ) const; + + /// Deletes an indexed property or array element. + MaybeOrValue Delete(uint32_t index ///< Property / element index + ) const; + + /// This operation can fail in case of Proxy.[[OwnPropertyKeys]] and + /// Proxy.[[GetOwnProperty]] calling into JavaScript. See: + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p + MaybeOrValue GetPropertyNames() const; ///< Get all property names + + /// Defines a property on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperty( + const PropertyDescriptor& + property ///< Descriptor for the property to be defined + ) const; + + /// Defines properties on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::initializer_list& properties + ///< List of descriptors for the properties to be defined + ) const; + + /// Defines properties on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::vector& properties + ///< Vector of descriptors for the properties to be defined + ) const; + + /// Checks if an object is an instance created by a constructor function. + /// + /// This is equivalent to the JavaScript `instanceof` operator. + /// + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue InstanceOf( + const Function& constructor ///< Constructor function + ) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) const; + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + class const_iterator; + + inline const_iterator begin() const; + + inline const_iterator end() const; + + class iterator; + + inline iterator begin(); + + inline iterator end(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#if NAPI_VERSION >= 8 + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Freeze() const; + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Seal() const; +#endif // NAPI_VERSION >= 8 +}; + +template +class External : public TypeTaggable { + public: + static External New(napi_env env, T* data); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static External New(napi_env env, T* data, Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static External New(napi_env env, + T* data, + Finalizer finalizeCallback, + Hint* finalizeHint); + + static void CheckCast(napi_env env, napi_value value); + + External(); + External(napi_env env, napi_value value); + + T* Data() const; +}; + +class Array : public Object { + public: + static Array New(napi_env env); + static Array New(napi_env env, size_t length); + + static void CheckCast(napi_env env, napi_value value); + + Array(); + Array(napi_env env, napi_value value); + + uint32_t Length() const; +}; + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS +class Object::const_iterator { + private: + enum class Type { BEGIN, END }; + + inline const_iterator(const Object* object, const Type type); + + public: + inline const_iterator& operator++(); + + inline bool operator==(const const_iterator& other) const; + + inline bool operator!=(const const_iterator& other) const; + + inline const std::pair> operator*() + const; + + private: + const Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; +}; + +class Object::iterator { + private: + enum class Type { BEGIN, END }; + + inline iterator(Object* object, const Type type); + + public: + inline iterator& operator++(); + + inline bool operator==(const iterator& other) const; + + inline bool operator!=(const iterator& other) const; + + inline std::pair> operator*(); + + private: + Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; +}; +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +/// A JavaScript array buffer value. +class ArrayBuffer : public Object { + public: + /// Creates a new ArrayBuffer instance over a new automatically-allocated + /// buffer. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + size_t byteLength ///< Length of the buffer to be allocated, in bytes + ); + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength ///< Length of the external buffer to be used by the + ///< array, in bytes + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env env, + /// void* externalData)` + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback, ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env + /// env, void* externalData, Hint* hint)` + Hint* finalizeHint ///< Hint (second parameter) to be passed to the + ///< finalize callback + ); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static void CheckCast(napi_env env, napi_value value); + + ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. + ArrayBuffer(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + void* Data(); ///< Gets a pointer to the data buffer. + size_t ByteLength(); ///< Gets the length of the array buffer in bytes. + +#if NAPI_VERSION >= 7 + bool IsDetached() const; + void Detach(); +#endif // NAPI_VERSION >= 7 +}; + +/// A JavaScript typed-array value with unknown array type. +/// +/// For type-specific operations, cast to a `TypedArrayOf` instance using the +/// `As()` method: +/// +/// Napi::TypedArray array = ... +/// if (t.TypedArrayType() == napi_int32_array) { +/// Napi::Int32Array int32Array = t.As(); +/// } +class TypedArray : public Object { + public: + static void CheckCast(napi_env env, napi_value value); + + TypedArray(); ///< Creates a new _empty_ TypedArray instance. + TypedArray(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + napi_typedarray_type TypedArrayType() + const; ///< Gets the type of this typed-array. + Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + + uint8_t ElementSize() + const; ///< Gets the size in bytes of one element in the array. + size_t ElementLength() const; ///< Gets the number of elements in the array. + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + protected: + /// !cond INTERNAL + napi_typedarray_type _type; + size_t _length; + + TypedArray(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length); + + template + static +#if defined(NAPI_HAS_CONSTEXPR) + constexpr +#endif + napi_typedarray_type + TypedArrayTypeForPrimitiveType() { + return std::is_same::value ? napi_int8_array + : std::is_same::value ? napi_uint8_array + : std::is_same::value ? napi_int16_array + : std::is_same::value ? napi_uint16_array + : std::is_same::value ? napi_int32_array + : std::is_same::value ? napi_uint32_array + : std::is_same::value ? napi_float32_array + : std::is_same::value ? napi_float64_array +#if NAPI_VERSION > 5 + : std::is_same::value ? napi_bigint64_array + : std::is_same::value ? napi_biguint64_array +#endif // NAPI_VERSION > 5 + : napi_int8_array; + } + /// !endcond +}; + +/// A JavaScript typed-array value with known array type. +/// +/// Note while it is possible to create and access Uint8 "clamped" arrays using +/// this class, the _clamping_ behavior is only applied in JavaScript. +template +class TypedArrayOf : public TypedArray { + public: + /// Creates a new TypedArray instance over a new automatically-allocated array + /// buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); + + /// Creates a new TypedArray instance over a provided array buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); + + static void CheckCast(napi_env env, napi_value value); + + TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. + TypedArrayOf(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + T& operator[](size_t index); ///< Gets or sets an element in the array. + const T& operator[](size_t index) const; ///< Gets an element in the array. + + /// Gets a pointer to the array's backing buffer. + /// + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + T* Data(); + + /// Gets a pointer to the array's backing buffer. + /// + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + const T* Data() const; + + private: + T* _data; + + TypedArrayOf(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length, + T* data); +}; + +/// The DataView provides a low-level interface for reading/writing multiple +/// number types in an ArrayBuffer irrespective of the platform's endianness. +class DataView : public Object { + public: + static DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); + + static void CheckCast(napi_env env, napi_value value); + + DataView(); ///< Creates a new _empty_ DataView instance. + DataView(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + void* Data() const; + + float GetFloat32(size_t byteOffset) const; + double GetFloat64(size_t byteOffset) const; + int8_t GetInt8(size_t byteOffset) const; + int16_t GetInt16(size_t byteOffset) const; + int32_t GetInt32(size_t byteOffset) const; + uint8_t GetUint8(size_t byteOffset) const; + uint16_t GetUint16(size_t byteOffset) const; + uint32_t GetUint32(size_t byteOffset) const; + + void SetFloat32(size_t byteOffset, float value) const; + void SetFloat64(size_t byteOffset, double value) const; + void SetInt8(size_t byteOffset, int8_t value) const; + void SetInt16(size_t byteOffset, int16_t value) const; + void SetInt32(size_t byteOffset, int32_t value) const; + void SetUint8(size_t byteOffset, uint8_t value) const; + void SetUint16(size_t byteOffset, uint16_t value) const; + void SetUint32(size_t byteOffset, uint32_t value) const; + + private: + template + T ReadData(size_t byteOffset) const; + + template + void WriteData(size_t byteOffset, T value) const; + + void* _data{}; + size_t _length{}; +}; + +class Function : public Object { + public: + using VoidCallback = void (*)(const CallbackInfo& info); + using Callback = Value (*)(const CallbackInfo& info); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const char* utf8name = nullptr, + void* data = nullptr); + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const std::string& utf8name, + void* data = nullptr); + + static void CheckCast(napi_env env, napi_value value); + + Function(); + Function(napi_env env, napi_value value); + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call(const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(size_t argc, const napi_value* args) const; + MaybeOrValue Call(napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, const std::vector& args) const; + MaybeOrValue Call(napi_value recv, const std::vector& args) const; + MaybeOrValue Call(napi_value recv, size_t argc, const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; + MaybeOrValue New(size_t argc, const napi_value* args) const; +}; + +class Promise : public Object { + public: + class Deferred { + public: + static Deferred New(napi_env env); + Deferred(napi_env env); + + Napi::Promise Promise() const; + Napi::Env Env() const; + + void Resolve(napi_value value) const; + void Reject(napi_value value) const; + + private: + napi_env _env; + napi_deferred _deferred; + napi_value _promise; + }; + + static void CheckCast(napi_env env, napi_value value); + + Promise(napi_env env, napi_value value); +}; + +template +class Buffer : public Uint8Array { + public: + static Buffer New(napi_env env, size_t length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + static Buffer New(napi_env env, T* data, size_t length); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static Buffer NewOrCopy(napi_env env, T* data, size_t length); + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); + + static Buffer Copy(napi_env env, const T* data, size_t length); + + static void CheckCast(napi_env env, napi_value value); + + Buffer(); + Buffer(napi_env env, napi_value value); + size_t Length() const; + T* Data() const; + + private: +}; + +/// Holds a counted reference to a value; initially a weak reference unless +/// otherwise specified, may be changed to/from a strong reference by adjusting +/// the refcount. +/// +/// The referenced value is not immediately destroyed when the reference count +/// is zero; it is merely then eligible for garbage-collection if there are no +/// other references to the value. +template +class Reference { + public: + static Reference New(const T& value, uint32_t initialRefcount = 0); + + Reference(); + Reference(napi_env env, napi_ref ref); + ~Reference(); + + // A reference can be moved but cannot be copied. + Reference(Reference&& other); + Reference& operator=(Reference&& other); + NAPI_DISALLOW_ASSIGN(Reference) + + operator napi_ref() const; + bool operator==(const Reference& other) const; + bool operator!=(const Reference& other) const; + + Napi::Env Env() const; + bool IsEmpty() const; + + // Note when getting the value of a Reference it is usually correct to do so + // within a HandleScope so that the value handle gets cleaned up efficiently. + T Value() const; + + uint32_t Ref() const; + uint32_t Unref() const; + void Reset(); + void Reset(const T& value, uint32_t refcount = 0); + + // Call this on a reference that is declared as static data, to prevent its + // destructor from running at program shutdown time, which would attempt to + // reset the reference when the environment is no longer valid. Avoid using + // this if at all possible. If you do need to use static data, MAKE SURE to + // warn your users that your addon is NOT threadsafe. + void SuppressDestruct(); + + protected: + Reference(const Reference&); + + /// !cond INTERNAL + napi_env _env; + napi_ref _ref; + /// !endcond + + private: + bool _suppressDestruct; +}; + +class ObjectReference : public Reference { + public: + ObjectReference(); + ObjectReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + ObjectReference(Reference&& other); + ObjectReference& operator=(Reference&& other); + ObjectReference(ObjectReference&& other); + ObjectReference& operator=(ObjectReference&& other); + NAPI_DISALLOW_ASSIGN(ObjectReference) + + MaybeOrValue Get(const char* utf8name) const; + MaybeOrValue Get(const std::string& utf8name) const; + MaybeOrValue Set(const char* utf8name, napi_value value) const; + MaybeOrValue Set(const char* utf8name, Napi::Value value) const; + MaybeOrValue Set(const char* utf8name, const char* utf8value) const; + MaybeOrValue Set(const char* utf8name, bool boolValue) const; + MaybeOrValue Set(const char* utf8name, double numberValue) const; + MaybeOrValue Set(const std::string& utf8name, napi_value value) const; + MaybeOrValue Set(const std::string& utf8name, Napi::Value value) const; + MaybeOrValue Set(const std::string& utf8name, + std::string& utf8value) const; + MaybeOrValue Set(const std::string& utf8name, bool boolValue) const; + MaybeOrValue Set(const std::string& utf8name, double numberValue) const; + + MaybeOrValue Get(uint32_t index) const; + MaybeOrValue Set(uint32_t index, const napi_value value) const; + MaybeOrValue Set(uint32_t index, const Napi::Value value) const; + MaybeOrValue Set(uint32_t index, const char* utf8value) const; + MaybeOrValue Set(uint32_t index, const std::string& utf8value) const; + MaybeOrValue Set(uint32_t index, bool boolValue) const; + MaybeOrValue Set(uint32_t index, double numberValue) const; + + protected: + ObjectReference(const ObjectReference&); +}; + +class FunctionReference : public Reference { + public: + FunctionReference(); + FunctionReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + FunctionReference(Reference&& other); + FunctionReference& operator=(Reference&& other); + FunctionReference(FunctionReference&& other); + FunctionReference& operator=(FunctionReference&& other); + NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; +}; + +// Shortcuts to creating a new reference with inferred type and refcount = 0. +template +Reference Weak(T value); +ObjectReference Weak(Object value); +FunctionReference Weak(Function value); + +// Shortcuts to creating a new reference with inferred type and refcount = 1. +template +Reference Persistent(T value); +ObjectReference Persistent(Object value); +FunctionReference Persistent(Function value); + +/// A persistent reference to a JavaScript error object. Use of this class +/// depends somewhat on whether C++ exceptions are enabled at compile time. +/// +/// ### Handling Errors With C++ Exceptions +/// +/// If C++ exceptions are enabled, then the `Error` class extends +/// `std::exception` and enables integrated error-handling for C++ exceptions +/// and JavaScript exceptions. +/// +/// If a Node-API call fails without executing any JavaScript code (for +/// example due to an invalid argument), then the Node-API wrapper +/// automatically converts and throws the error as a C++ exception of type +/// `Napi::Error`. Or if a JavaScript function called by C++ code via Node-API +/// throws a JavaScript exception, then the Node-API wrapper automatically +/// converts and throws it as a C++ exception of type `Napi::Error`. +/// +/// If a C++ exception of type `Napi::Error` escapes from a Node-API C++ +/// callback, then the Node-API wrapper automatically converts and throws it +/// as a JavaScript exception. Therefore, catching a C++ exception of type +/// `Napi::Error` prevents a JavaScript exception from being thrown. +/// +/// #### Example 1A - Throwing a C++ exception: +/// +/// Napi::Env env = ... +/// throw Napi::Error::New(env, "Example exception"); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propagated as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 2A - Propagating a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propagated as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 3A - Handling a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result; +/// try { +/// result = jsFunctionThatThrows({ arg1, arg2 }); +/// } catch (const Napi::Error& e) { +/// cerr << "Caught JavaScript exception: " + e.what(); +/// } +/// +/// Since the exception was caught here, it will not be propagated as a +/// JavaScript exception. +/// +/// ### Handling Errors Without C++ Exceptions +/// +/// If C++ exceptions are disabled (by defining +/// `NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS`) then this class does not extend +/// `std::exception`, and APIs in the `Napi` namespace do not throw C++ +/// exceptions when they fail. Instead, they raise _pending_ JavaScript +/// exceptions and return _empty_ `Value`s. Calling code should check +/// `Value::IsEmpty()` before attempting to use a returned value, and may use +/// methods on the `Env` class to check for, get, and clear a pending JavaScript +/// exception. If the pending exception is not cleared, it will be thrown when +/// the native callback returns to JavaScript. +/// +/// #### Example 1B - Throwing a JS exception +/// +/// Napi::Env env = ... +/// Napi::Error::New(env, "Example +/// exception").ThrowAsJavaScriptException(); return; +/// +/// After throwing a JS exception, the code should generally return +/// immediately from the native callback, after performing any necessary +/// cleanup. +/// +/// #### Example 2B - Propagating a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) return; +/// +/// An empty value result from a Node-API call indicates an error occurred, +/// and a JavaScript exception is pending. To let the exception propagate, the +/// code should generally return immediately from the native callback, after +/// performing any necessary cleanup. +/// +/// #### Example 3B - Handling a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) { +/// Napi::Error e = env.GetAndClearPendingException(); +/// cerr << "Caught JavaScript exception: " + e.Message(); +/// } +/// +/// Since the exception was cleared here, it will not be propagated as a +/// JavaScript exception after the native callback returns. +class Error : public ObjectReference +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + , + public std::exception +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +{ + public: + static Error New(napi_env env); + static Error New(napi_env env, const char* message); + static Error New(napi_env env, const std::string& message); + + static NAPI_NO_RETURN void Fatal(const char* location, const char* message); + + Error(); + Error(napi_env env, napi_value value); + + // An error can be moved or copied. + Error(Error&& other); + Error& operator=(Error&& other); + Error(const Error&); + Error& operator=(const Error&); + + const std::string& Message() const NAPI_NOEXCEPT; + void ThrowAsJavaScriptException() const; + + Object Value() const; + +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + const char* what() const NAPI_NOEXCEPT override; +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + + protected: + /// !cond INTERNAL + using create_error_fn = napi_status (*)(napi_env envb, + napi_value code, + napi_value msg, + napi_value* result); + + template + static TError New(napi_env env, + const char* message, + size_t length, + create_error_fn create_error); + /// !endcond + + private: + static inline const char* ERROR_WRAP_VALUE() NAPI_NOEXCEPT; + mutable std::string _message; +}; + +class TypeError : public Error { + public: + static TypeError New(napi_env env, const char* message); + static TypeError New(napi_env env, const std::string& message); + + TypeError(); + TypeError(napi_env env, napi_value value); +}; + +class RangeError : public Error { + public: + static RangeError New(napi_env env, const char* message); + static RangeError New(napi_env env, const std::string& message); + + RangeError(); + RangeError(napi_env env, napi_value value); +}; + +#if NAPI_VERSION > 8 +class SyntaxError : public Error { + public: + static SyntaxError New(napi_env env, const char* message); + static SyntaxError New(napi_env env, const std::string& message); + + SyntaxError(); + SyntaxError(napi_env env, napi_value value); +}; +#endif // NAPI_VERSION > 8 + +class CallbackInfo { + public: + CallbackInfo(napi_env env, napi_callback_info info); + ~CallbackInfo(); + + // Disallow copying to prevent multiple free of _dynamicArgs + NAPI_DISALLOW_ASSIGN_COPY(CallbackInfo) + + Napi::Env Env() const; + Value NewTarget() const; + bool IsConstructCall() const; + size_t Length() const; + const Value operator[](size_t index) const; + Value This() const; + void* Data() const; + void SetData(void* data); + explicit operator napi_callback_info() const; + + private: + const size_t _staticArgCount = 6; + napi_env _env; + napi_callback_info _info; + napi_value _this; + size_t _argc; + napi_value* _argv; + napi_value _staticArgs[6]{}; + napi_value* _dynamicArgs; + void* _data; +}; + +class PropertyDescriptor { + public: + using GetterCallback = Napi::Value (*)(const Napi::CallbackInfo& info); + using SetterCallback = void (*)(const Napi::CallbackInfo& info); + +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + napi_value name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + napi_value name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + Name name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + PropertyDescriptor(napi_property_descriptor desc); + + operator napi_property_descriptor&(); + operator const napi_property_descriptor&() const; + + private: + napi_property_descriptor _desc; +}; + +/// Property descriptor for use with `ObjectWrap::DefineClass()`. +/// +/// This is different from the standalone `PropertyDescriptor` because it is +/// specific to each `ObjectWrap` subclass. This prevents using descriptors +/// from a different class when defining a new class (preventing the callbacks +/// from having incorrect `this` pointers). +template +class ClassPropertyDescriptor { + public: + ClassPropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} + + operator napi_property_descriptor&() { return _desc; } + operator const napi_property_descriptor&() const { return _desc; } + + private: + napi_property_descriptor _desc; +}; + +template +struct MethodCallbackData { + TCallback callback; + void* data; +}; + +template +struct AccessorCallbackData { + TGetterCallback getterCallback; + TSetterCallback setterCallback; + void* data; +}; + +template +class InstanceWrap { + public: + using InstanceVoidMethodCallback = void (T::*)(const CallbackInfo& info); + using InstanceMethodCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceGetterCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceSetterCallback = void (T::*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + protected: + static void AttachPropData(napi_env env, + napi_value value, + const napi_property_descriptor* prop); + + private: + using This = InstanceWrap; + + using InstanceVoidMethodCallbackData = + MethodCallbackData; + using InstanceMethodCallbackData = + MethodCallbackData; + using InstanceAccessorCallbackData = + AccessorCallbackData; + + static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceSetterCallbackWrapper(napi_env env, + napi_callback_info info); + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct SetterTag {}; + + template + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return nullptr; + } +}; + +/// Base class to be extended by C++ classes exposed to JavaScript; each C++ +/// class instance gets "wrapped" by a JavaScript object that is managed by this +/// class. +/// +/// At initialization time, the `DefineClass()` method must be used to +/// hook up the accessor and method callbacks. It takes a list of +/// property descriptors, which can be constructed via the various +/// static methods on the base class. +/// +/// #### Example: +/// +/// class Example: public Napi::ObjectWrap { +/// public: +/// static void Initialize(Napi::Env& env, Napi::Object& target) { +/// Napi::Function constructor = DefineClass(env, "Example", { +/// InstanceAccessor<&Example::GetSomething, +/// &Example::SetSomething>("value"), +/// InstanceMethod<&Example::DoSomething>("doSomething"), +/// }); +/// target.Set("Example", constructor); +/// } +/// +/// Example(const Napi::CallbackInfo& info); // Constructor +/// Napi::Value GetSomething(const Napi::CallbackInfo& info); +/// void SetSomething(const Napi::CallbackInfo& info, const Napi::Value& +/// value); Napi::Value DoSomething(const Napi::CallbackInfo& info); +/// } +template +class ObjectWrap : public InstanceWrap, public Reference { + public: + ObjectWrap(const CallbackInfo& callbackInfo); + virtual ~ObjectWrap(); + + static T* Unwrap(Object wrapper); + + // Methods exposed to JavaScript must conform to one of these callback + // signatures. + using StaticVoidMethodCallback = void (*)(const CallbackInfo& info); + using StaticMethodCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticGetterCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticSetterCallback = void (*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static Function DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list& properties, + void* data = nullptr); + static Function DefineClass(Napi::Env env, + const char* utf8name, + const std::vector& properties, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor StaticValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo); + virtual void Finalize(Napi::Env env); + virtual void Finalize(BasicEnv env); + + private: + using This = ObjectWrap; + + static napi_value ConstructorCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticSetterCallbackWrapper(napi_env env, + napi_callback_info info); + static void FinalizeCallback(node_addon_api_basic_env env, + void* data, + void* hint); + + static void PostFinalizeCallback(napi_env env, void* data, void* hint); + + static Function DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* props, + void* data = nullptr); + + using StaticVoidMethodCallbackData = + MethodCallbackData; + using StaticMethodCallbackData = MethodCallbackData; + + using StaticAccessorCallbackData = + AccessorCallbackData; + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct StaticSetterTag {}; + + template + static napi_callback WrapStaticSetter(StaticSetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapStaticSetter(StaticSetterTag) + NAPI_NOEXCEPT { + return nullptr; + } + + bool _construction_failed = true; + bool _finalized = false; +}; + +class HandleScope { + public: + HandleScope(napi_env env, napi_handle_scope scope); + explicit HandleScope(Napi::Env env); + ~HandleScope(); + + // Disallow copying to prevent double close of napi_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(HandleScope) + + operator napi_handle_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_handle_scope _scope; +}; + +class EscapableHandleScope { + public: + EscapableHandleScope(napi_env env, napi_escapable_handle_scope scope); + explicit EscapableHandleScope(Napi::Env env); + ~EscapableHandleScope(); + + // Disallow copying to prevent double close of napi_escapable_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(EscapableHandleScope) + + operator napi_escapable_handle_scope() const; + + Napi::Env Env() const; + Value Escape(napi_value escapee); + + private: + napi_env _env; + napi_escapable_handle_scope _scope; +}; + +#if (NAPI_VERSION > 2) +class CallbackScope { + public: + CallbackScope(napi_env env, napi_callback_scope scope); + CallbackScope(napi_env env, napi_async_context context); + virtual ~CallbackScope(); + + // Disallow copying to prevent double close of napi_callback_scope + NAPI_DISALLOW_ASSIGN_COPY(CallbackScope) + + operator napi_callback_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_callback_scope _scope; +}; +#endif + +class AsyncContext { + public: + explicit AsyncContext(napi_env env, const char* resource_name); + explicit AsyncContext(napi_env env, + const char* resource_name, + const Object& resource); + virtual ~AsyncContext(); + + AsyncContext(AsyncContext&& other); + AsyncContext& operator=(AsyncContext&& other); + NAPI_DISALLOW_ASSIGN_COPY(AsyncContext) + + operator napi_async_context() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_async_context _context; +}; + +#if NAPI_HAS_THREADS +class AsyncWorker { + public: + virtual ~AsyncWorker(); + + NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) + + operator napi_async_work() const; + + Napi::Env Env() const; + + void Queue(); + void Cancel(); + void SuppressDestruct(); + + ObjectReference& Receiver(); + FunctionReference& Callback(); + + virtual void OnExecute(Napi::Env env); + virtual void OnWorkComplete(Napi::Env env, napi_status status); + + protected: + explicit AsyncWorker(const Function& callback); + explicit AsyncWorker(const Function& callback, const char* resource_name); + explicit AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncWorker(const Object& receiver, const Function& callback); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + + explicit AsyncWorker(Napi::Env env); + explicit AsyncWorker(Napi::Env env, const char* resource_name); + explicit AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource); + + virtual void Execute() = 0; + virtual void OnOK(); + virtual void OnError(const Error& e); + virtual void Destroy(); + virtual std::vector GetResult(Napi::Env env); + + void SetError(const std::string& error); + + private: + static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); + static inline void OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker); + + napi_env _env; + napi_async_work _work; + ObjectReference _receiver; + FunctionReference _callback; + std::string _error; + bool _suppress_destruct; +}; +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +class ThreadSafeFunction { + public: + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + ThreadSafeFunction(); + ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall() const; + + // This API may be called from any thread. + template + napi_status BlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status BlockingCall(DataType* data, Callback callback) const; + + // This API may be called from any thread. + napi_status NonBlockingCall() const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(DataType* data, Callback callback) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + struct ConvertibleContext { + template + operator T*() { + return static_cast(context); + } + void* context; + }; + + // This API may be called from any thread. + ConvertibleContext GetContext() const; + + private: + using CallbackWrapper = std::function; + + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + napi_status CallInternal(CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const; + + static void CallJS(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + napi_threadsafe_function _tsfn; +}; + +// A TypedThreadSafeFunction by default has no context (nullptr) and can +// accept any type (void) to its CallJs. +template +class TypedThreadSafeFunction { + public: + // This API may only be called from the main thread. + // Helper function that returns nullptr if running Node-API 5+, otherwise a + // non-empty, no-op Function. This provides the ability to specify at + // compile-time a callback parameter to `New` that safely does no action + // when targeting _any_ Node-API version. +#if NAPI_VERSION > 4 + static std::nullptr_t EmptyFunctionFactory(Napi::Env env); +#else + static Napi::Function EmptyFunctionFactory(Napi::Env env); +#endif + static Napi::Function FunctionOrEmpty(Napi::Env env, + Napi::Function& callback); + +#if NAPI_VERSION > 4 + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); +#endif + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + CallbackType callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + TypedThreadSafeFunction(); + TypedThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall(DataType* data = nullptr) const; + + // This API may be called from any thread. + napi_status NonBlockingCall(DataType* data = nullptr) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + // This API may be called from any thread. + ContextType* GetContext() const; + + private: + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + static void CallJsInternal(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + protected: + napi_threadsafe_function _tsfn; +}; +template +class AsyncProgressWorkerBase : public AsyncWorker { + public: + virtual void OnWorkProgress(DataType* data) = 0; + class ThreadSafeData { + public: + ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) + : _asyncprogressworker(asyncprogressworker), _data(data) {} + + AsyncProgressWorkerBase* asyncprogressworker() { + return _asyncprogressworker; + }; + DataType* data() { return _data; }; + + private: + AsyncProgressWorkerBase* _asyncprogressworker; + DataType* _data; + }; + void OnWorkComplete(Napi::Env env, napi_status status) override; + + protected: + explicit AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); + virtual ~AsyncProgressWorkerBase(); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); +#endif + + static inline void OnAsyncWorkProgress(Napi::Env env, + Napi::Function jsCallback, + void* data); + + napi_status NonBlockingCall(DataType* data); + + private: + ThreadSafeFunction _tsfn; + bool _work_completed = false; + napi_status _complete_status; + static inline void OnThreadSafeFunctionFinalize( + Napi::Env env, void* data, AsyncProgressWorkerBase* context); +}; + +template +class AsyncProgressWorker : public AsyncProgressWorkerBase { + public: + virtual ~AsyncProgressWorker(); + + class ExecutionProgress { + friend class AsyncProgressWorker; + + public: + void Signal() const; + void Send(const T* data, size_t count) const; + + private: + explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} + AsyncProgressWorker* const _worker; + }; + + void OnWorkProgress(void*) override; + + protected: + explicit AsyncProgressWorker(const Function& callback); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorker(Napi::Env env); + explicit AsyncProgressWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal(); + void SendProgress_(const T* data, size_t count); + + std::mutex _mutex; + T* _asyncdata; + size_t _asyncsize; + bool _signaled; +}; + +template +class AsyncProgressQueueWorker + : public AsyncProgressWorkerBase> { + public: + virtual ~AsyncProgressQueueWorker(){}; + + class ExecutionProgress { + friend class AsyncProgressQueueWorker; + + public: + void Signal() const; + void Send(const T* data, size_t count) const; + + private: + explicit ExecutionProgress(AsyncProgressQueueWorker* worker) + : _worker(worker) {} + AsyncProgressQueueWorker* const _worker; + }; + + void OnWorkComplete(Napi::Env env, napi_status status) override; + void OnWorkProgress(std::pair*) override; + + protected: + explicit AsyncProgressQueueWorker(const Function& callback); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressQueueWorker(Napi::Env env); + explicit AsyncProgressQueueWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal() const; + void SendProgress_(const T* data, size_t count); +}; +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +// Memory management. +class MemoryManagement { + public: + static int64_t AdjustExternalMemory(BasicEnv env, int64_t change_in_bytes); +}; + +// Version management +class VersionManagement { + public: + static uint32_t GetNapiVersion(BasicEnv env); + static const napi_node_version* GetNodeVersion(BasicEnv env); +}; + +#if NAPI_VERSION > 5 +template +class Addon : public InstanceWrap { + public: + static inline Object Init(Env env, Object exports); + static T* Unwrap(Object wrapper); + + protected: + using AddonProp = ClassPropertyDescriptor; + void DefineAddon(Object exports, + const std::initializer_list& props); + Napi::Object DefineProperties(Object object, + const std::initializer_list& props); + + private: + Object entry_point_; +}; +#endif // NAPI_VERSION > 5 + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi + +// Inline implementations of all the above class methods are included here. +#include "napi-inl.h" + +#endif // SRC_NAPI_H_ diff --git a/date.go b/date.go new file mode 100644 index 0000000..cdca89b --- /dev/null +++ b/date.go @@ -0,0 +1,29 @@ +package napi + +import ( + "time" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type Date struct{ value } + +// Convert [ValueType] to [*Date] +func ToDate(o ValueType) *Date { return &Date{o} } + +func CreateDate(env EnvType, date time.Time) (*Date, error) { + value, err := mustValueErr(napi.CreateDate(env.NapiValue(), float64(date.UnixMilli()))) + if err != nil { + return nil, err + } + return &Date{value: &Value{env: env, valueOf: value}}, nil +} + +// Get time from [*Date] object. +func (d Date) Time() (time.Time, error) { + timeFloat, err := mustValueErr(napi.GetDateValue(d.NapiEnv(), d.NapiValue())) + if err != nil { + return time.Time{}, err + } + return time.UnixMilli(int64(timeFloat)), nil +} diff --git a/docs/examples/async-promise/main.go b/docs/examples/async-promise/main.go deleted file mode 100644 index 6180fc4..0000000 --- a/docs/examples/async-promise/main.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "fmt" - "time" - - "github.com/akshayganeshen/napi-go" - "github.com/akshayganeshen/napi-go/entry" -) - -func init() { - entry.Export("getPromise", GetPromiseHandler) -} - -func GetPromiseHandler(env napi.Env, info napi.CallbackInfo) napi.Value { - result, _ := napi.CreatePromise(env) - asyncResourceName, _ := napi.CreateStringUtf8( - env, - "napi-go/async-promise-example", - ) - - var asyncWork napi.AsyncWork - asyncWork, _ = napi.CreateAsyncWork( - env, - nil, asyncResourceName, - func(env napi.Env) { - fmt.Printf("AsyncExecuteCallback(start)\n") - defer fmt.Printf("AsyncExecuteCallback(stop)\n") - time.Sleep(time.Second) - }, - func(env napi.Env, status napi.Status) { - defer napi.DeleteAsyncWork(env, asyncWork) - - if status == napi.StatusCancelled { - fmt.Printf("AsyncCompleteCallback(cancelled)\n") - return - } - - fmt.Printf("AsyncCompleteCallback\n") - resolution, _ := napi.CreateStringUtf8(env, "resolved") - napi.ResolveDeferred(env, result.Deferred, resolution) - }, - ) - napi.QueueAsyncWork(env, asyncWork) - - return result.Value -} - -func main() {} diff --git a/docs/examples/callback/main.go b/docs/examples/callback/main.go deleted file mode 100644 index 3c5d64b..0000000 --- a/docs/examples/callback/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "github.com/akshayganeshen/napi-go" - "github.com/akshayganeshen/napi-go/entry" -) - -func init() { - entry.Export("getCallback", GetCallbackHandler) -} - -func GetCallbackHandler(env napi.Env, info napi.CallbackInfo) napi.Value { - result, _ := napi.CreateFunction( - env, - "callback", - func(env napi.Env, info napi.CallbackInfo) napi.Value { - result, _ := napi.CreateStringUtf8(env, "hello world") - return result - }, - ) - - return result -} - -func main() {} diff --git a/docs/examples/describe-args/main.go b/docs/examples/describe-args/main.go deleted file mode 100644 index 1474927..0000000 --- a/docs/examples/describe-args/main.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "github.com/akshayganeshen/napi-go" - "github.com/akshayganeshen/napi-go/entry" -) - -func init() { - entry.Export("describeArgs", DescribeArgsHandler) -} - -func DescribeArgsHandler(env napi.Env, info napi.CallbackInfo) napi.Value { - extractedInfo, _ := napi.GetCbInfo(env, info) - result, _ := napi.CreateArrayWithLength(env, len(extractedInfo.Args)) - for i, arg := range extractedInfo.Args { - vt, _ := napi.Typeof(env, arg) - dv, _ := napi.CreateStringUtf8(env, DescribeValueType(vt)) - napi.SetElement(env, result, i, dv) - } - - return result -} - -func DescribeValueType(vt napi.ValueType) string { - switch vt { - case napi.ValueTypeUndefined: - return "undefined" - case napi.ValueTypeNull: - return "null" - case napi.ValueTypeBoolean: - return "boolean" - case napi.ValueTypeNumber: - return "number" - case napi.ValueTypeString: - return "string" - case napi.ValueTypeSymbol: - return "symbol" - case napi.ValueTypeObject: - return "object" - case napi.ValueTypeFunction: - return "function" - case napi.ValueTypeExternal: - return "external" - case napi.ValueTypeBigint: - return "bigint" - - default: - return "other" - } -} - -func main() {} diff --git a/docs/examples/hello-world/main.go b/docs/examples/hello-world/main.go deleted file mode 100644 index e74dfcc..0000000 --- a/docs/examples/hello-world/main.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/akshayganeshen/napi-go" - "github.com/akshayganeshen/napi-go/entry" -) - -func init() { - entry.Export("hello", HelloHandler) -} - -func HelloHandler(env napi.Env, info napi.CallbackInfo) napi.Value { - fmt.Println("hello world!") - return nil -} - -func main() {} diff --git a/docs/examples/js/main.go b/docs/examples/js/main.go deleted file mode 100644 index ec39751..0000000 --- a/docs/examples/js/main.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "fmt" - "time" - - "github.com/akshayganeshen/napi-go" - "github.com/akshayganeshen/napi-go/entry" - "github.com/akshayganeshen/napi-go/js" -) - -func init() { - entry.Export("getMap", GetMapHandler) - entry.Export("getCallback", js.AsCallback(GetCallback)) - entry.Export("getArray", js.AsCallback(GetArray)) - entry.Export("getPromiseResolve", js.AsCallback(GetPromiseResolve)) - entry.Export("getPromiseReject", js.AsCallback(GetPromiseReject)) -} - -func GetMapHandler(env napi.Env, info napi.CallbackInfo) napi.Value { - jsEnv := js.AsEnv(env) - - return jsEnv.ValueOf( - map[string]any{ - "string": "hello world", - "number": 123, - "bool": false, - "undefined": jsEnv.Undefined(), - "null": nil, - "function": jsEnv.FuncOf( - func(env js.Env, this js.Value, args []js.Value) any { - return "hello world" - }, - ), - }, - ).Value -} - -func GetCallback(env js.Env, this js.Value, args []js.Value) any { - return func(env js.Env, this js.Value, args []js.Value) any { - return map[string]any{ - "this": this, - "args": args, - } - } -} - -func GetArray(env js.Env, this js.Value, args []js.Value) any { - return []any{ - "hello world", - 123, - true, - map[string]any{ - "key": "value", - }, - } -} - -func GetPromiseResolve(env js.Env, this js.Value, args []js.Value) any { - promise := env.NewPromise() - - go func() { - time.Sleep(time.Second) - promise.Resolve("resolved") - }() - - return promise -} - -func GetPromiseReject(env js.Env, this js.Value, args []js.Value) any { - promise := env.NewPromise() - - go func() { - time.Sleep(time.Second) - promise.Reject(fmt.Errorf("rejected")) - }() - - return promise -} - -func main() {} diff --git a/entry/entry.go b/entry/entry.go new file mode 100644 index 0000000..83d5635 --- /dev/null +++ b/entry/entry.go @@ -0,0 +1,66 @@ +package entry + +/* +#cgo CFLAGS: -DDEBUG +#cgo CFLAGS: -D_DEBUG +#cgo CFLAGS: -DV8_ENABLE_CHECKS +#cgo CFLAGS: -DNAPI_EXPERIMENTAL +#cgo CFLAGS: -I/usr/local/include/node +#cgo CXXFLAGS: -std=c++11 + +#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup +#cgo darwin LDFLAGS: -Wl,-no_pie +#cgo darwin LDFLAGS: -Wl,-search_paths_first +#cgo (darwin && amd64) LDFLAGS: -arch x86_64 +#cgo (darwin && arm64) LDFLAGS: -arch arm64 + +#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all + +#cgo LDFLAGS: -L${SRCDIR} + +#include +#include "./entry.h" +*/ +import "C" + +import ( + "fmt" + + gonapi "sirherobrine23.com.br/Sirherobrine23/napi-go" + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type registerCallback func(env napi.Env, object napi.Value) + +var modFuncInit = []registerCallback{} + +//export initializeModule +func initializeModule(cEnv C.napi_env, cExports C.napi_value) C.napi_value { + env, exports := napi.Env(cEnv), napi.Value(cExports) + napi.InitializeInstanceData(env) + + defer func() { + if err := recover(); err != nil { + switch v := err.(type) { + case error: + gonapi.ThrowError(gonapi.N_APIEnv(env), "", v.Error()) + default: + gonapi.ThrowError(gonapi.N_APIEnv(env), "", fmt.Sprintf("%s", v)) + } + } + }() + + for _, registerCall := range modFuncInit { + registerCall(env, exports) + } + + return cExports +} + +func Register(fn func(env gonapi.EnvType, export *gonapi.Object)) { + modFuncInit = append(modFuncInit, func(env napi.Env, object napi.Value) { + registerEnv := gonapi.N_APIEnv(env) + registerObj := gonapi.ToObject(gonapi.N_APIValue(registerEnv, object)) + fn(registerEnv, registerObj) + }) +} diff --git a/entry/entry.h b/entry/entry.h index a264feb..890ed1e 100644 --- a/entry/entry.h +++ b/entry/entry.h @@ -7,9 +7,9 @@ extern "C" { #endif /* __cplusplus */ -// InitializeModule is a N-API module initialization function. -// InitializeModule is suitable for use as a napi_addon_register_func. -extern napi_value InitializeModule( +// initializeModule is a N-API module initialization function. +// initializeModule is suitable for use as a napi_addon_register_func. +extern napi_value initializeModule( napi_env env, napi_value exports ); diff --git a/entry/exports.go b/entry/exports.go deleted file mode 100644 index 9894d61..0000000 --- a/entry/exports.go +++ /dev/null @@ -1,19 +0,0 @@ -package entry - -import ( - "github.com/akshayganeshen/napi-go" -) - -type napiGoExport struct { - Name string - Callback napi.Callback -} - -var napiGoGlobalExports []napiGoExport - -func Export(name string, callback napi.Callback) { - napiGoGlobalExports = append(napiGoGlobalExports, napiGoExport{ - Name: name, - Callback: callback, - }) -} diff --git a/entry/napi_module.c b/entry/napi_module.c index 72e2384..1d44a79 100644 --- a/entry/napi_module.c +++ b/entry/napi_module.c @@ -2,4 +2,4 @@ #include "./entry.h" -NAPI_MODULE(napiGo, InitializeModule) +NAPI_MODULE(napiGo, initializeModule) diff --git a/entry/napi_module.go b/entry/napi_module.go deleted file mode 100644 index 93d7724..0000000 --- a/entry/napi_module.go +++ /dev/null @@ -1,26 +0,0 @@ -package entry - -/* -#include - -#include "./entry.h" -*/ -import "C" - -import ( - "github.com/akshayganeshen/napi-go" -) - -//export InitializeModule -func InitializeModule(cEnv C.napi_env, cExports C.napi_value) C.napi_value { - env, exports := napi.Env(cEnv), napi.Value(cExports) - napi.InitializeInstanceData(env) - - for _, export := range napiGoGlobalExports { - cb, _ := napi.CreateFunction(env, export.Name, export.Callback) - name, _ := napi.CreateStringUtf8(env, export.Name) - napi.SetProperty(env, exports, name, cb) - } - - return cExports -} diff --git a/entry/node_api_cgo_flags.go b/entry/node_api_cgo_flags.go deleted file mode 100644 index aa87517..0000000 --- a/entry/node_api_cgo_flags.go +++ /dev/null @@ -1,21 +0,0 @@ -package entry - -/* -#cgo CFLAGS: -DDEBUG -#cgo CFLAGS: -D_DEBUG -#cgo CFLAGS: -DV8_ENABLE_CHECKS -#cgo CFLAGS: -DNAPI_EXPERIMENTAL -#cgo CFLAGS: -I/usr/local/include/node -#cgo CXXFLAGS: -std=c++11 - -#cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup -#cgo darwin LDFLAGS: -Wl,-no_pie -#cgo darwin LDFLAGS: -Wl,-search_paths_first -#cgo darwin LDFLAGS: -arch x86_64 - -#cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all - -#cgo LDFLAGS: -L${SRCDIR} -#cgo LDFLAGS: -stdlib=libc++ -*/ -import "C" diff --git a/error.go b/error.go new file mode 100644 index 0000000..1bb8702 --- /dev/null +++ b/error.go @@ -0,0 +1,37 @@ +package napi + +import ( + "runtime" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type Error struct{ value } + +func ToError(o ValueType) *Error { return &Error{o} } + +func CreateError(env EnvType, msg string) (*Error, error) { + napiMsg, err := CreateString(env, msg) + if err != nil { + return nil, err + } + napiValue, err := mustValueErr(napi.CreateError(env.NapiValue(), nil, napiMsg.NapiValue())) + if err != nil { + return nil, err + } + return ToError(N_APIValue(env, napiValue)), nil +} + +func (er *Error) ThrowAsJavaScriptException() error { + return singleMustValueErr(napi.Throw(er.NapiEnv(), er.NapiValue())) +} + +// This throws a JavaScript Error with the text provided. +func ThrowError(env EnvType, code, err string) error { + if code == "" { + stackTraceBuf := make([]byte, 8192) + stackTraceSz := runtime.Stack(stackTraceBuf, false) + code = string(stackTraceBuf[:stackTraceSz]) + } + return singleMustValueErr(napi.ThrowError(env.NapiValue(), code, err)) +} diff --git a/example/test.go b/example/test.go new file mode 100644 index 0000000..86a6c62 --- /dev/null +++ b/example/test.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "net/netip" + + "sirherobrine23.com.br/Sirherobrine23/napi-go" + "sirherobrine23.com.br/Sirherobrine23/napi-go/entry" + "sirherobrine23.com.br/Sirherobrine23/napi-go/js" +) + +type Test struct { + Int int + String string + Sub []any +} + +func init() { + entry.Register(func(env napi.EnvType, export *napi.Object) { + inNode, _ := napi.CreateString(env, "from golang napi string") + inNode2, _ := napi.CopyBuffer(env, []byte{1, 0, 244, 21}) + toGoReflect := &Test{ + Int: 14, + String: "From golang", + Sub: []any{ + 1, + []string{"test", "gopher"}, + []bool{false, true}, + []int{23, 244, 10, 2024, 2025, 2000}, + map[string]string{"exampleMap": "test"}, + map[int]string{1: "one"}, + map[bool]string{false: "false", true: "true"}, + map[[2]string]string{[2]string{"go"}: "example"}, + netip.IPv4Unspecified(), + netip.IPv6Unspecified(), + netip.AddrPortFrom(netip.IPv6Unspecified(), 19132), + nil, + true, + false, + inNode, + inNode2, + func() { + println("called in go") + }, + }, + } + + napiStruct, err := js.ValueOf(env, toGoReflect) + if err != nil { + panic(err) + } + export.Set("goStruct", napiStruct) + + fnCall, err := js.GoFuncOf(env, func(call ...any) (string, error) { + d, err := json.MarshalIndent(call, "", " ") + if err == nil { + println(string(d)) + } + return string(d), err + }) + if err != nil { + panic(err) + } + export.Set("printAny", fnCall) + + fnCallStruct, err := js.GoFuncOf(env, func(call ...Test) (string, error) { + d, err := json.MarshalIndent(call, "", " ") + if err == nil { + println(string(d)) + } + return string(d), err + }) + if err != nil { + panic(err) + } + export.Set("printTestStruct", fnCallStruct) + }) +} + +func main() {} diff --git a/function.go b/function.go new file mode 100644 index 0000000..67b504a --- /dev/null +++ b/function.go @@ -0,0 +1,96 @@ +package napi + +import ( + "fmt" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type Function struct{ value } + +// Function to call on Javascript caller +type Callback func(env EnvType, this ValueType, args []ValueType) (ValueType, error) + +// Convert [ValueType] to [*Function] +func ToFunction(o ValueType) *Function { return &Function{o} } + +func CreateFunction(env EnvType, name string, callback Callback) (*Function, error) { + return CreateFunctionNapi(env, name, func(napiEnv napi.Env, info napi.CallbackInfo) napi.Value { + env := N_APIEnv(napiEnv) + cbInfo, err := mustValueErr(napi.GetCbInfo(napiEnv, info)) + if err != nil { + ThrowError(env, "", err.Error()) + return nil + } + + this := N_APIValue(env, cbInfo.This) + args := make([]ValueType, len(cbInfo.Args)) + for i, cbArg := range cbInfo.Args { + args[i] = N_APIValue(env, cbArg) + } + + defer func() { + if err := recover(); err != nil { + switch v := err.(type) { + case error: + ThrowError(env, "", v.Error()) + default: + ThrowError(env, "", fmt.Sprintf("panic recover: %s", err)) + } + } + }() + + res, err := callback(env, this, args) + switch { + case err != nil: + ThrowError(env, "", err.Error()) + return nil + case res == nil: + und, _ := env.Undefined() + return und.NapiValue() + default: + typeOf, _ := res.Type() + if typeOf == TypeError { + ToError(res).ThrowAsJavaScriptException() + return nil + } + return res.NapiValue() + } + }) +} + +func CreateFunctionNapi(env EnvType, name string, callback napi.Callback) (*Function, error) { + fnCall, err := mustValueErr(napi.CreateFunction(env.NapiValue(), name, callback)) + if err != nil { + return nil, err + } + return ToFunction(N_APIValue(env, fnCall)), nil +} + +func (fn *Function) internalCall(this napi.Value, argc int, argv []napi.Value) (ValueType, error) { + // napi_call_function(env, global, add_two, argc, argv, &return_val); + res, err := mustValueErr(napi.CallFunction(fn.NapiEnv(), this, fn.NapiValue(), argc, argv)) + if err != nil { + return nil, err + } + return N_APIValue(fn.Env(), res), nil +} + +// Call function with custom global/this value +func (fn *Function) CallWithGlobal(this ValueType, args ...ValueType) (ValueType, error) { + argc := len(args) + argv := make([]napi.Value, argc) + for index := range argc { + argv[index] = args[index].NapiValue() + } + return fn.internalCall(this.NapiValue(), argc, argv) +} + +// Call function with args +func (fn *Function) Call(args ...ValueType) (ValueType, error) { + global, err := fn.Env().Global() + if err != nil { + return nil, err + } + return fn.CallWithGlobal(global, args...) +} diff --git a/go.mod b/go.mod index 79f7bb2..7991e9b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/akshayganeshen/napi-go +module sirherobrine23.com.br/Sirherobrine23/napi-go -go 1.18 +go 1.24 diff --git a/async_work.go b/internal/napi/async_work.go similarity index 100% rename from async_work.go rename to internal/napi/async_work.go diff --git a/callback.go b/internal/napi/callback.go similarity index 100% rename from callback.go rename to internal/napi/callback.go diff --git a/callback_info.go b/internal/napi/callback_info.go similarity index 100% rename from callback_info.go rename to internal/napi/callback_info.go diff --git a/env.go b/internal/napi/env.go similarity index 100% rename from env.go rename to internal/napi/env.go diff --git a/instance_data.go b/internal/napi/instance_data.go similarity index 100% rename from instance_data.go rename to internal/napi/instance_data.go diff --git a/internal/napi/js_native_api.go b/internal/napi/js_native_api.go new file mode 100644 index 0000000..06e6abf --- /dev/null +++ b/internal/napi/js_native_api.go @@ -0,0 +1,1294 @@ +package napi + +/* +#include +#include +*/ +import "C" + +import ( + "unsafe" +) + +func GetUndefined(env Env) (Value, Status) { + var result Value + status := Status(C.napi_get_undefined( + C.napi_env(env), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetNull(env Env) (Value, Status) { + var result Value + status := Status(C.napi_get_null( + C.napi_env(env), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetGlobal(env Env) (Value, Status) { + var result Value + status := Status(C.napi_get_global( + C.napi_env(env), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetBoolean(env Env, value bool) (Value, Status) { + var result Value + status := Status(C.napi_get_boolean( + C.napi_env(env), + C.bool(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateObject(env Env) (Value, Status) { + var result Value + status := Status(C.napi_create_object( + C.napi_env(env), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateArray(env Env) (Value, Status) { + var result Value + status := Status(C.napi_create_array( + C.napi_env(env), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateArrayWithLength(env Env, length int) (Value, Status) { + var result Value + status := Status(C.napi_create_array_with_length( + C.napi_env(env), + C.size_t(length), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateDouble(env Env, value float64) (Value, Status) { + var result Value + status := Status(C.napi_create_double( + C.napi_env(env), + C.double(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateStringUtf8(env Env, str string) (Value, Status) { + cstr := C.CString(str) + defer C.free(unsafe.Pointer(cstr)) + + var result Value + status := Status(C.napi_create_string_utf8( + C.napi_env(env), + cstr, + C.size_t(len([]byte(str))), // must pass number of bytes + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateSymbol(env Env, description Value) (Value, Status) { + var result Value + status := Status(C.napi_create_symbol( + C.napi_env(env), + C.napi_value(description), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateFunction(env Env, name string, cb Callback) (Value, Status) { + provider, status := getInstanceData(env) + if status != StatusOK || provider == nil { + return nil, status + } + + return provider.GetCallbackData().CreateCallback(env, name, cb) +} + +func CreateError(env Env, code, msg Value) (Value, Status) { + var result Value + status := Status(C.napi_create_error( + C.napi_env(env), + C.napi_value(code), + C.napi_value(msg), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func Typeof(env Env, value Value) (ValueType, Status) { + var result ValueType + status := Status(C.napi_typeof( + C.napi_env(env), + C.napi_value(value), + (*C.napi_valuetype)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetValueDouble(env Env, value Value) (float64, Status) { + var result float64 + status := Status(C.napi_get_value_double( + C.napi_env(env), + C.napi_value(value), + (*C.double)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetValueBool(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_get_value_bool( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetValueStringUtf8(env Env, value Value) (string, Status) { + // call napi_get_value_string_utf8 twice + // first is to get number of bytes + // second is to populate the actual string buffer + bufsize := C.size_t(0) + var strsize C.size_t + + status := Status(C.napi_get_value_string_utf8( + C.napi_env(env), + C.napi_value(value), + nil, + bufsize, + &strsize, + )) + + if status != StatusOK { + return "", status + } + + // ensure there is room for the null terminator as well + strsize++ + cstr := (*C.char)(C.malloc(C.sizeof_char * strsize)) + defer C.free(unsafe.Pointer(cstr)) + + status = Status(C.napi_get_value_string_utf8( + C.napi_env(env), + C.napi_value(value), + cstr, + strsize, + &strsize, + )) + + if status != StatusOK { + return "", status + } + + return C.GoStringN( + (*C.char)(cstr), + (C.int)(strsize), + ), status +} + +func GetValueStringUtf16(env Env, value Value) ([]uint16, Status) { + bufsize := C.size_t(0) + var strsize C.size_t + + status := Status(C.napi_get_value_string_utf16(C.napi_env(env), C.napi_value(value), nil, bufsize, &strsize)) + if status != StatusOK { + return nil, status + } + + strsize++ + cstr := (*C.char16_t)(C.malloc(C.sizeof_char * strsize)) + defer C.free(unsafe.Pointer(cstr)) + + status = Status(C.napi_get_value_string_utf16(C.napi_env(env), C.napi_value(value), cstr, strsize, &strsize)) + if status != StatusOK { + return nil, status + } + runes := make([]uint16, strsize) + copy(runes, (*[1 << 30]uint16)(unsafe.Pointer(cstr))[:strsize:strsize]) + return runes, status +} + +func SetProperty(env Env, object, key, value Value) Status { + return Status(C.napi_set_property( + C.napi_env(env), + C.napi_value(object), + C.napi_value(key), + C.napi_value(value), + )) +} + +func SetElement(env Env, object Value, index int, value Value) Status { + return Status(C.napi_set_element( + C.napi_env(env), + C.napi_value(object), + C.uint32_t(index), + C.napi_value(value), + )) +} + +func StrictEquals(env Env, lhs, rhs Value) (bool, Status) { + var result bool + status := Status(C.napi_strict_equals( + C.napi_env(env), + C.napi_value(lhs), + C.napi_value(rhs), + (*C.bool)(&result), + )) + return result, status +} + +type GetCbInfoResult struct { + Args []Value + This Value +} + +func GetCbInfo(env Env, info CallbackInfo) (GetCbInfoResult, Status) { + // call napi_get_cb_info twice + // first is to get total number of arguments + // second is to populate the actual arguments + argc := C.size_t(0) + status := Status(C.napi_get_cb_info( + C.napi_env(env), + C.napi_callback_info(info), + &argc, + nil, + nil, + nil, + )) + + if status != StatusOK { + return GetCbInfoResult{}, status + } + + argv := make([]Value, int(argc)) + var cArgv unsafe.Pointer + if argc > 0 { + cArgv = unsafe.Pointer(&argv[0]) // must pass element pointer + } + + var thisArg Value + + status = Status(C.napi_get_cb_info( + C.napi_env(env), + C.napi_callback_info(info), + &argc, + (*C.napi_value)(cArgv), + (*C.napi_value)(unsafe.Pointer(&thisArg)), + nil, + )) + + return GetCbInfoResult{ + Args: argv, + This: thisArg, + }, status +} + +func Throw(env Env, err Value) Status { + return Status(C.napi_throw( + C.napi_env(env), + C.napi_value(err), + )) +} + +func ThrowError(env Env, code, msg string) Status { + codeCStr, msgCCstr := C.CString(code), C.CString(msg) + defer C.free(unsafe.Pointer(codeCStr)) + defer C.free(unsafe.Pointer(msgCCstr)) + + return Status(C.napi_throw_error( + C.napi_env(env), + codeCStr, + msgCCstr, + )) +} + +func CreatePromise(env Env) (Promise, Status) { + var result Promise + status := Status(C.napi_create_promise( + C.napi_env(env), + (*C.napi_deferred)(unsafe.Pointer(&result.Deferred)), + (*C.napi_value)(unsafe.Pointer(&result.Value)), + )) + return result, status +} + +func ResolveDeferred(env Env, deferred Deferred, resolution Value) Status { + return Status(C.napi_resolve_deferred( + C.napi_env(env), + C.napi_deferred(deferred), + C.napi_value(resolution), + )) +} + +func RejectDeferred(env Env, deferred Deferred, rejection Value) Status { + return Status(C.napi_reject_deferred( + C.napi_env(env), + C.napi_deferred(deferred), + C.napi_value(rejection), + )) +} + +func SetInstanceData(env Env, data any) Status { + provider, status := getInstanceData(env) + if status != StatusOK || provider == nil { + return status + } + + provider.SetUserData(data) + return status +} + +func GetInstanceData(env Env) (any, Status) { + provider, status := getInstanceData(env) + if status != StatusOK || provider == nil { + return nil, status + } + + return provider.GetUserData(), status +} + +func CreateExternal(env Env, data unsafe.Pointer, finalize Finalize, finalizeHint unsafe.Pointer) (Value, Status) { + var result Value + finalizer := FinalizeToFinalizer(finalize) + status := Status(C.napi_create_external( + C.napi_env(env), + data, + C.napi_finalize(unsafe.Pointer(&finalizer)), + finalizeHint, + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetValueInt32(env Env, value Value) (int32, Status) { + var result C.int32_t + status := Status(C.napi_get_value_int32( + C.napi_env(env), + C.napi_value(value), + &result, + )) + return int32(result), status +} + +func GetValueUint32(env Env, value Value) (uint32, Status) { + var result uint32 + status := Status(C.napi_get_value_uint32( + C.napi_env(env), + C.napi_value(value), + (*C.uint32_t)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetValueInt64(env Env, value Value) (int64, Status) { + var result int64 + status := Status(C.napi_get_value_int64( + C.napi_env(env), + C.napi_value(value), + (*C.int64_t)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetValueBigIntInt64(env Env, value Value) (int64, bool, Status) { + var result int64 + var lossless bool + status := Status(C.napi_get_value_bigint_int64( + C.napi_env(env), + C.napi_value(value), + (*C.int64_t)(unsafe.Pointer(&result)), + (*C.bool)(unsafe.Pointer(&lossless)), + )) + return result, lossless, status +} + +func GetValueBigIntWords(env Env, value Value, signBit int, wordCount int, words *uint64) Status { + return Status(C.napi_get_value_bigint_words( + C.napi_env(env), + C.napi_value(value), + (*C.int)(unsafe.Pointer(&signBit)), + (*C.size_t)(unsafe.Pointer(&wordCount)), + (*C.uint64_t)(unsafe.Pointer(words)), + )) +} + +func GetValueExternal(env Env, value Value) (unsafe.Pointer, Status) { + var result unsafe.Pointer + status := Status(C.napi_get_value_external( + C.napi_env(env), + C.napi_value(value), + &result, + )) + return result, status +} + +func CoerceToBool(env Env, value Value) (Value, Status) { + var result Value + status := Status(C.napi_coerce_to_bool( + C.napi_env(env), + C.napi_value(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CoerceToNumber(env Env, value Value) (Value, Status) { + var result Value + status := Status(C.napi_coerce_to_number( + C.napi_env(env), + C.napi_value(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CoerceToObject(env Env, value Value) (Value, Status) { + var result Value + status := Status(C.napi_coerce_to_object( + C.napi_env(env), + C.napi_value(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CoerceToString(env Env, value Value) (Value, Status) { + var result Value + status := Status(C.napi_coerce_to_string( + C.napi_env(env), + C.napi_value(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateBuffer(env Env, length int) (Value, Status) { + var result Value + status := Status(C.napi_create_buffer( + C.napi_env(env), + C.size_t(length), + nil, + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateBufferCopy(env Env, data []byte) (Value, Status) { + var result Value + status := Status(C.napi_create_buffer_copy( + C.napi_env(env), + C.size_t(len(data)), + unsafe.Pointer(&data[0]), + nil, + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetBufferInfo(env Env, value Value) (*byte, int, Status) { + var data *byte + var length C.size_t + + dataPtr := unsafe.Pointer(&data) + status := Status(C.napi_get_buffer_info(C.napi_env(env), C.napi_value(value), &dataPtr, &length)) + return data, int(length), status +} + +func GetBufferInfoSize(env Env, value Value) (int, Status) { + var length C.size_t + status := Status(C.napi_get_buffer_info(C.napi_env(env), C.napi_value(value), nil, &length)) + return int(length), status +} + +func GetBufferInfoData(env Env, value Value) (buff []byte, status Status) { + var data *byte + var length C.size_t + + dataPtr := unsafe.Pointer(&data) + status = Status(C.napi_get_buffer_info(C.napi_env(env), C.napi_value(value), &dataPtr, &length)) + if status == StatusOK { + buff = make([]byte, length) + copy(buff, (*[1 << 30]byte)(unsafe.Pointer(data))[:length:length]) + } + return +} + +func GetArrayLength(env Env, value Value) (int, Status) { + var length C.uint32_t + status := Status(C.napi_get_array_length( + C.napi_env(env), + C.napi_value(value), + &length, + )) + return int(length), status +} + +func GetPrototype(env Env, value Value) (Value, Status) { + var result Value + status := Status(C.napi_get_prototype( + C.napi_env(env), + C.napi_value(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func InstanceOf(env Env, object, constructor Value) (bool, Status) { + var result bool + status := Status(C.napi_instanceof( + C.napi_env(env), + C.napi_value(object), + C.napi_value(constructor), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsArray(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_array( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsBuffer(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_buffer( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsError(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_error( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsPromise(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_promise( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsTypedArray(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_typedarray( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetTypedArrayInfo(env Env, value Value) (TypedArrayType, int, *byte, Value, int, Status) { + var type_ TypedArrayType + var length C.size_t + var data *byte + var arrayBuffer Value + var byteOffset C.size_t + + dataPtr := unsafe.Pointer(&data) + status := Status(C.napi_get_typedarray_info( + C.napi_env(env), + C.napi_value(value), + (*C.napi_typedarray_type)(unsafe.Pointer(&type_)), + &length, + &dataPtr, + (*C.napi_value)(unsafe.Pointer(&arrayBuffer)), + &byteOffset, + )) + return type_, int(length), data, arrayBuffer, int(byteOffset), status +} + +func CreateTypedArray(env Env, type_ TypedArrayType, length int, arrayBuffer Value, byteOffset int) (Value, Status) { + var result Value + status := Status(C.napi_create_typedarray( + C.napi_env(env), + C.napi_typedarray_type(type_), + C.size_t(length), + C.napi_value(arrayBuffer), + C.size_t(byteOffset), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func AdjustExternalMemory(env Env, change int64) (int64, Status) { + var result int64 + status := Status(C.napi_adjust_external_memory( + C.napi_env(env), + C.int64_t(change), + (*C.int64_t)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateDataView(env Env, length int, arrayBuffer Value, byteOffset int) (Value, Status) { + var result Value + status := Status(C.napi_create_dataview( + C.napi_env(env), + C.size_t(length), + C.napi_value(arrayBuffer), + C.size_t(byteOffset), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetDataViewInfo(env Env, value Value) (int, *byte, Value, int, Status) { + var length C.size_t + var data *byte + var arrayBuffer Value + var byteOffset C.size_t + + dataPtr := unsafe.Pointer(&data) + status := Status(C.napi_get_dataview_info( + C.napi_env(env), + C.napi_value(value), + &length, + &dataPtr, + (*C.napi_value)(unsafe.Pointer(&arrayBuffer)), + &byteOffset, + )) + return int(length), data, arrayBuffer, int(byteOffset), status +} + +func GetAllPropertyNames(env Env, object Value, keyMode KeyCollectionMode, keyFilter KeyFilter, keyConversion KeyConversion) (Value, Status) { + var result Value + status := Status(C.napi_get_all_property_names( + C.napi_env(env), + C.napi_value(object), + C.napi_key_collection_mode(keyMode), + C.napi_key_filter(keyFilter), + C.napi_key_conversion(keyConversion), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func HasOwnProperty(env Env, object, key Value) (bool, Status) { + var result bool + status := Status(C.napi_has_own_property( + C.napi_env(env), + C.napi_value(object), + C.napi_value(key), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func HasProperty(env Env, object, key Value) (bool, Status) { + var result bool + status := Status(C.napi_has_property( + C.napi_env(env), + C.napi_value(object), + C.napi_value(key), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetPropertyNames(env Env, object Value) (Value, Status) { + var result Value + status := Status(C.napi_get_property_names( + C.napi_env(env), + C.napi_value(object), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func DefineProperties(env Env, object Value, properties []PropertyDescriptor) Status { + return Status(C.napi_define_properties( + C.napi_env(env), + C.napi_value(object), + C.size_t(len(properties)), + (*C.napi_property_descriptor)(unsafe.Pointer(&properties[0])), + )) +} + +// Wrap function to use the Finalizer type internally +func Wrap(env Env, jsObject Value, nativeObject unsafe.Pointer, finalize Finalize, finalizeHint unsafe.Pointer) Status { + var result Reference + status := Status(C.napi_wrap( + C.napi_env(env), + C.napi_value(jsObject), + nativeObject, + C.napi_finalize(unsafe.Pointer(&finalize)), + finalizeHint, + (*C.napi_ref)(unsafe.Pointer(&result)), + )) + return status +} + +func Unwrap(env Env, jsObject Value) (unsafe.Pointer, Status) { + var nativeObject unsafe.Pointer + status := Status(C.napi_unwrap( + C.napi_env(env), + C.napi_value(jsObject), + &nativeObject, + )) + return nativeObject, status +} + +func RemoveWrap(env Env, jsObject Value) Status { + var result unsafe.Pointer + return Status(C.napi_remove_wrap( + C.napi_env(env), + C.napi_value(jsObject), + &result, + )) +} + +func OpenHandleScope(env Env) (HandleScope, Status) { + var scope HandleScope + status := Status(C.napi_open_handle_scope( + C.napi_env(env), + (*C.napi_handle_scope)(unsafe.Pointer(&scope)), + )) + return scope, status +} + +func CloseHandleScope(env Env, scope HandleScope) Status { + return Status(C.napi_close_handle_scope( + C.napi_env(env), + C.napi_handle_scope(unsafe.Pointer(&scope)), + )) +} + +func OpenEscapableHandleScope(env Env) (EscapableHandleScope, Status) { + var scope EscapableHandleScope + status := Status(C.napi_open_escapable_handle_scope( + C.napi_env(env), + (*C.napi_escapable_handle_scope)(unsafe.Pointer(&scope)), + )) + return scope, status +} + +func CloseEscapableHandleScope(env Env, scope EscapableHandleScope) Status { + return Status(C.napi_close_escapable_handle_scope( + C.napi_env(env), + C.napi_escapable_handle_scope(unsafe.Pointer(&scope)), + )) +} + +func EscapeHandle(env Env, scope EscapableHandleScope, escapee Value) (Value, Status) { + var result Value + status := Status(C.napi_escape_handle( + C.napi_env(env), + C.napi_escapable_handle_scope(unsafe.Pointer(&scope)), + C.napi_value(escapee), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateReference(env Env, value Value, initialRefcount int) (Reference, Status) { + var ref Reference + status := Status(C.napi_create_reference( + C.napi_env(env), + C.napi_value(value), + C.uint32_t(initialRefcount), + (*C.napi_ref)(unsafe.Pointer(&ref)), + )) + return ref, status +} + +func DeleteReference(env Env, ref Reference) Status { + return Status(C.napi_delete_reference( + C.napi_env(env), + C.napi_ref(unsafe.Pointer(&ref)), + )) +} + +func ReferenceRef(env Env, ref Reference) (int, Status) { + var result C.uint32_t + status := Status(C.napi_reference_ref( + C.napi_env(env), + C.napi_ref(unsafe.Pointer(&ref)), + &result, + )) + return int(result), status +} + +func ReferenceUnref(env Env, ref Reference) (int, Status) { + var result C.uint32_t + status := Status(C.napi_reference_unref( + C.napi_env(env), + C.napi_ref(ref.Ref), + &result, + )) + return int(result), status +} + +func GetReferenceValue(env Env, ref Reference) (Value, Status) { + var result Value + status := Status(C.napi_get_reference_value( + C.napi_env(env), + C.napi_ref(unsafe.Pointer(&ref)), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetValueBigIntUint64(env Env, value Value) (uint64, bool, Status) { + var result uint64 + var lossless bool + status := Status(C.napi_get_value_bigint_uint64( + C.napi_env(env), + C.napi_value(value), + (*C.uint64_t)(unsafe.Pointer(&result)), + (*C.bool)(unsafe.Pointer(&lossless)), + )) + return result, lossless, status +} + +func CreateBigIntInt64(env Env, value int64) (Value, Status) { + var result Value + status := Status(C.napi_create_bigint_int64( + C.napi_env(env), + C.int64_t(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateBigIntUint64(env Env, value uint64) (Value, Status) { + var result Value + status := Status(C.napi_create_bigint_uint64( + C.napi_env(env), + C.uint64_t(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateBigIntWords(env Env, signBit int, wordCount int, words *uint64) (Value, Status) { + var result Value + status := Status(C.napi_create_bigint_words( + C.napi_env(env), + C.int(signBit), + C.size_t(wordCount), + (*C.uint64_t)(unsafe.Pointer(words)), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsDate(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_date( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsDetachedArrayBuffer(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_detached_arraybuffer( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func DetachArrayBuffer(env Env, value Value) Status { + return Status(C.napi_detach_arraybuffer( + C.napi_env(env), + C.napi_value(value), + )) +} + +func CreateArrayBuffer(env Env, length int) (Value, *byte, Status) { + var result Value + var data *byte + dataPtr := unsafe.Pointer(&data) + status := Status(C.napi_create_arraybuffer( + C.napi_env(env), + C.size_t(length), + &dataPtr, + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, data, status +} + +func GetArrayBufferInfo(env Env, value Value) (*byte, int, Status) { + var data *byte + var length C.size_t + dataPtr := unsafe.Pointer(&data) + status := Status(C.napi_get_arraybuffer_info( + C.napi_env(env), + C.napi_value(value), + &dataPtr, + &length, + )) + return data, int(length), status +} + +func CreateExternalArrayBuffer(env Env, data unsafe.Pointer, length int, finalize Finalize, finalizeHint unsafe.Pointer) (Value, Status) { + var result Value + finalizer := FinalizeToFinalizer(finalize) + status := Status(C.napi_create_external_arraybuffer( + C.napi_env(env), + data, + C.size_t(length), + C.napi_finalize(unsafe.Pointer(&finalizer)), + finalizeHint, + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetElement(env Env, object Value, index int) (Value, Status) { + var result Value + status := Status(C.napi_get_element( + C.napi_env(env), + C.napi_value(object), + C.uint32_t(index), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetProperty(env Env, object, key Value) (Value, Status) { + var result Value + status := Status(C.napi_get_property( + C.napi_env(env), + C.napi_value(object), + C.napi_value(key), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func DeleteProperty(env Env, object, key Value) (bool, Status) { + var result bool + status := Status(C.napi_delete_property( + C.napi_env(env), + C.napi_value(object), + C.napi_value(key), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func SetNamedProperty(env Env, object Value, name string, value Value) Status { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + return Status(C.napi_set_named_property( + C.napi_env(env), + C.napi_value(object), + cname, + C.napi_value(value), + )) +} + +func GetNamedProperty(env Env, object Value, name string) (Value, Status) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + var result Value + status := Status(C.napi_get_named_property( + C.napi_env(env), + C.napi_value(object), + cname, + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func HasNamedProperty(env Env, object Value, name string) (bool, Status) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + var result bool + status := Status(C.napi_has_named_property( + C.napi_env(env), + C.napi_value(object), + cname, + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func HasElement(env Env, object Value, index int) (bool, Status) { + var result bool + status := Status(C.napi_has_element( + C.napi_env(env), + C.napi_value(object), + C.uint32_t(index), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func DeleteElement(env Env, object Value, index int) (bool, Status) { + var result bool + status := Status(C.napi_delete_element( + C.napi_env(env), + C.napi_value(object), + C.uint32_t(index), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func ObjectFreeze(env Env, object Value) Status { + return Status(C.napi_object_freeze( + C.napi_env(env), + C.napi_value(object), + )) +} + +func ObjectSeal(env Env, object Value) Status { + return Status(C.napi_object_seal( + C.napi_env(env), + C.napi_value(object), + )) +} + +func ThrowTypeError(env Env, code, msg string) Status { + codeCStr, msgCCstr := C.CString(code), C.CString(msg) + defer C.free(unsafe.Pointer(codeCStr)) + defer C.free(unsafe.Pointer(msgCCstr)) + + return Status(C.napi_throw_type_error( + C.napi_env(env), + codeCStr, + msgCCstr, + )) +} + +func ThrowRangeError(env Env, code, msg string) Status { + codeCStr, msgCCstr := C.CString(code), C.CString(msg) + defer C.free(unsafe.Pointer(codeCStr)) + defer C.free(unsafe.Pointer(msgCCstr)) + + return Status(C.napi_throw_range_error( + C.napi_env(env), + codeCStr, + msgCCstr, + )) +} + +func CreateTypeError(env Env, code, msg Value) (Value, Status) { + var result Value + status := Status(C.napi_create_type_error( + C.napi_env(env), + C.napi_value(code), + C.napi_value(msg), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateRangeError(env Env, code, msg Value) (Value, Status) { + var result Value + status := Status(C.napi_create_range_error( + C.napi_env(env), + C.napi_value(code), + C.napi_value(msg), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsExceptionPending(env Env) (bool, Status) { + var result bool + status := Status(C.napi_is_exception_pending( + C.napi_env(env), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetAndClearLastException(env Env) (Value, Status) { + var result Value + status := Status(C.napi_get_and_clear_last_exception( + C.napi_env(env), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +type CallbackScope struct { + scope C.napi_callback_scope +} + +// CloseCallbackScope Function to close a callback scope +func CloseCallbackScope(env Env, scope CallbackScope) Status { + return Status(C.napi_close_callback_scope( + C.napi_env(env), + scope.scope, + )) +} + +// GetExtendedErrorInfo Function to retrieve extended error information +func GetExtendedErrorInfo(env Env) (*C.napi_extended_error_info, Status) { + var errorInfo *C.napi_extended_error_info + status := Status(C.napi_get_last_error_info( + C.napi_env(env), + &errorInfo, + )) + return errorInfo, status +} + +func CreateInt32(env Env, value int32) (Value, Status) { + var result Value + status := Status(C.napi_create_int32( + C.napi_env(env), + C.int32_t(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateUint32(env Env, value uint32) (Value, Status) { + var result Value + status := Status(C.napi_create_uint32( + C.napi_env(env), + C.uint32_t(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateInt64(env Env, value int64) (Value, Status) { + var result Value + status := Status(C.napi_create_int64( + C.napi_env(env), + C.int64_t(value), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateStringLatin1(env Env, str string) (Value, Status) { + cstr := C.CString(str) + defer C.free(unsafe.Pointer(cstr)) + + var result Value + status := Status(C.napi_create_string_latin1( + C.napi_env(env), + cstr, + C.size_t(len([]byte(str))), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateStringUtf16(env Env, str []uint16) (Value, Status) { + var result Value + status := Status(C.napi_create_string_utf16( + C.napi_env(env), + (*C.char16_t)(unsafe.Pointer(&str[0])), + C.size_t(len(str)), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CallFunction(env Env, recv Value, fn Value, argc int, argv []Value) (Value, Status) { + var result Value + status := Status(C.napi_call_function( + C.napi_env(env), + C.napi_value(recv), + C.napi_value(fn), + C.size_t(argc), + (*C.napi_value)(unsafe.Pointer(&argv[0])), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetNewTarget(env Env, info CallbackInfo) (Value, Status) { + var result Value + status := Status(C.napi_get_new_target( + C.napi_env(env), + C.napi_callback_info(info), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func NewInstance(env Env, constructor Value, argc int, argv []Value) (Value, Status) { + var result Value + status := Status(C.napi_new_instance( + C.napi_env(env), + C.napi_value(constructor), + C.size_t(argc), + (*C.napi_value)(unsafe.Pointer(&argv[0])), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsDataView(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_dataview( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func IsArrayBuffer(env Env, value Value) (bool, Status) { + var result bool + status := Status(C.napi_is_arraybuffer( + C.napi_env(env), + C.napi_value(value), + (*C.bool)(unsafe.Pointer(&result)), + )) + return result, status +} + +func GetDateValue(env Env, value Value) (float64, Status) { + var result float64 + status := Status(C.napi_get_date_value( + C.napi_env(env), + C.napi_value(value), + (*C.double)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreateDate(env Env, time float64) (Value, Status) { + var result Value + status := Status(C.napi_create_date( + C.napi_env(env), + (C.double)(time), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} diff --git a/internal/napi/key_type.go b/internal/napi/key_type.go new file mode 100644 index 0000000..375702f --- /dev/null +++ b/internal/napi/key_type.go @@ -0,0 +1,34 @@ +package napi + +/* +#include +*/ +import "C" + +// KeyCollectionMode type +type KeyCollectionMode C.napi_key_collection_mode + +const ( + KeyIncludePrototypes KeyCollectionMode = C.napi_key_include_prototypes + KeyOwnOnly KeyCollectionMode = C.napi_key_own_only +) + +// KeyFilter type +type KeyFilter C.napi_key_filter + +const ( + KeyAllProperties KeyFilter = C.napi_key_all_properties + KeyWritable KeyFilter = C.napi_key_writable + KeyEnumerable KeyFilter = C.napi_key_enumerable + KeyConfigurable KeyFilter = C.napi_key_configurable + KeySkipStrings KeyFilter = C.napi_key_skip_strings + KeySkipSymbols KeyFilter = C.napi_key_skip_symbols +) + +// KeyConversion type +type KeyConversion C.napi_key_conversion + +const ( + KeyKeepNumbers KeyConversion = C.napi_key_keep_numbers + KeyNumbersToStrings KeyConversion = C.napi_key_numbers_to_strings +) diff --git a/node_api_cgo_flags.go b/internal/napi/napi.go similarity index 81% rename from node_api_cgo_flags.go rename to internal/napi/napi.go index 9c8f595..3c31953 100644 --- a/node_api_cgo_flags.go +++ b/internal/napi/napi.go @@ -11,11 +11,11 @@ package napi #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup #cgo darwin LDFLAGS: -Wl,-no_pie #cgo darwin LDFLAGS: -Wl,-search_paths_first -#cgo darwin LDFLAGS: -arch x86_64 +// #cgo darwin amd64 LDFLAGS: -arch x86_64 +// #cgo darwin arm64 LDFLAGS: -arch arm64 #cgo linux LDFLAGS: -Wl,-unresolved-symbols=ignore-all #cgo LDFLAGS: -L${SRCDIR} -#cgo LDFLAGS: -stdlib=libc++ */ -import "C" +import "C" \ No newline at end of file diff --git a/node_api.go b/internal/napi/node_api.go similarity index 51% rename from node_api.go rename to internal/napi/node_api.go index aca723e..33352fa 100644 --- a/node_api.go +++ b/internal/napi/node_api.go @@ -1,6 +1,7 @@ package napi /* +#include #include */ import "C" @@ -113,11 +114,12 @@ func CreateThreadsafeFunction( func CallThreadsafeFunction( fn ThreadsafeFunction, + mode ThreadsafeFunctionCallMode, ) Status { return Status(C.napi_call_threadsafe_function( C.napi_threadsafe_function(fn), nil, - C.napi_tsfn_blocking, + C.napi_threadsafe_function_call_mode(mode), )) } @@ -129,9 +131,108 @@ func AcquireThreadsafeFunction(fn ThreadsafeFunction) Status { func ReleaseThreadsafeFunction( fn ThreadsafeFunction, + mode ThreadsafeFunctionReleaseMode, ) Status { return Status(C.napi_release_threadsafe_function( C.napi_threadsafe_function(fn), - C.napi_tsfn_release, + C.napi_threadsafe_function_release_mode(mode), )) } + +func GetThreadsafeFunctionContext( + fn ThreadsafeFunction, +) (unsafe.Pointer, Status) { + var context unsafe.Pointer + status := Status(C.napi_get_threadsafe_function_context( + C.napi_threadsafe_function(fn), + &context, + )) + return context, status +} + +func RefThreadsafeFunction(env Env, fn ThreadsafeFunction) Status { + return Status(C.napi_ref_threadsafe_function( + C.napi_env(env), + C.napi_threadsafe_function(fn), + )) +} + +func UnrefThreadsafeFunction(env Env, fn ThreadsafeFunction) Status { + return Status(C.napi_unref_threadsafe_function( + C.napi_env(env), + C.napi_threadsafe_function(fn), + )) +} + +func ThrowSyntaxError(env Env, code, msg string) Status { + codeCStr, msgCStr := C.CString(code), C.CString(msg) + defer C.free(unsafe.Pointer(codeCStr)) + defer C.free(unsafe.Pointer(msgCStr)) + + return Status(C.node_api_throw_syntax_error( + C.napi_env(env), + codeCStr, + msgCStr, + )) +} + +func CreateSyntaxError(env Env, code, msg Value) (Value, Status) { + var result Value + status := Status(C.node_api_create_syntax_error( + C.napi_env(env), + C.napi_value(code), + C.napi_value(msg), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func SymbolFor(env Env, description string) (Value, Status) { + var result Value + status := Status(C.node_api_symbol_for( + C.napi_env(env), + C.CString(description), + C.size_t(len(description)), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreatePropertyKeyLatin1(env Env, str string) (Value, Status) { + cstr := C.CString(str) + defer C.free(unsafe.Pointer(cstr)) + + var result Value + status := Status(C.node_api_create_property_key_latin1( + C.napi_env(env), + cstr, + C.size_t(len([]byte(str))), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreatePropertyKeyUtf16(env Env, str []uint16) (Value, Status) { + var result Value + status := Status(C.node_api_create_property_key_utf16( + C.napi_env(env), + (*C.char16_t)(unsafe.Pointer(&str[0])), + C.size_t(len(str)), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} + +func CreatePropertyKeyUtf8(env Env, str string) (Value, Status) { + cstr := C.CString(str) + defer C.free(unsafe.Pointer(cstr)) + + var result Value + status := Status(C.node_api_create_property_key_utf8( + C.napi_env(env), + cstr, + C.size_t(len([]byte(str))), + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} diff --git a/node_version.go b/internal/napi/node_version.go similarity index 100% rename from node_version.go rename to internal/napi/node_version.go diff --git a/promise.go b/internal/napi/promise.go similarity index 100% rename from promise.go rename to internal/napi/promise.go diff --git a/internal/napi/property_attrib_type.go b/internal/napi/property_attrib_type.go new file mode 100644 index 0000000..fbfcf0c --- /dev/null +++ b/internal/napi/property_attrib_type.go @@ -0,0 +1,18 @@ +package napi + +/* +#include +*/ +import "C" + +type PropertyAttributes C.napi_property_attributes + +const ( + Default PropertyAttributes = C.napi_default + Writable PropertyAttributes = C.napi_writable + Enumerable PropertyAttributes = C.napi_enumerable + Configurable PropertyAttributes = C.napi_configurable + Static PropertyAttributes = C.napi_static + DefaultMethod PropertyAttributes = C.napi_default_method + DefaultJSProperty PropertyAttributes = C.napi_default_jsproperty +) diff --git a/status.go b/internal/napi/status.go similarity index 100% rename from status.go rename to internal/napi/status.go diff --git a/threadsafe_function.go b/internal/napi/threadsafe_function.go similarity index 100% rename from threadsafe_function.go rename to internal/napi/threadsafe_function.go diff --git a/internal/napi/threadsafe_function_type.go b/internal/napi/threadsafe_function_type.go new file mode 100644 index 0000000..14a4d2e --- /dev/null +++ b/internal/napi/threadsafe_function_type.go @@ -0,0 +1,20 @@ +package napi + +/* +#include +*/ +import "C" + +type ThreadsafeFunctionReleaseMode C.napi_threadsafe_function_release_mode + +const ( + Release ThreadsafeFunctionReleaseMode = C.napi_tsfn_release + Abort ThreadsafeFunctionReleaseMode = C.napi_tsfn_abort +) + +type ThreadsafeFunctionCallMode C.napi_threadsafe_function_call_mode + +const ( + NonBlocking ThreadsafeFunctionCallMode = C.napi_tsfn_nonblocking + Blocking ThreadsafeFunctionCallMode = C.napi_tsfn_blocking +) diff --git a/internal/napi/typed_array_type.go b/internal/napi/typed_array_type.go new file mode 100644 index 0000000..e63c82f --- /dev/null +++ b/internal/napi/typed_array_type.go @@ -0,0 +1,22 @@ +package napi + +/* +#include +*/ +import "C" + +type TypedArrayType C.napi_typedarray_type + +const ( + TypedArrayInt8Array TypedArrayType = C.napi_int8_array + TypedArrayUint8Array TypedArrayType = C.napi_uint8_array + TypedArrayUint8ClampedArray TypedArrayType = C.napi_uint8_clamped_array + TypedArrayInt16Array TypedArrayType = C.napi_int16_array + TypedArrayUint16Array TypedArrayType = C.napi_uint16_array + TypedArrayInt32Array TypedArrayType = C.napi_int32_array + TypedArrayUint32Array TypedArrayType = C.napi_uint32_array + TypedArrayFloat32Array TypedArrayType = C.napi_float32_array + TypedArrayFloat64Array TypedArrayType = C.napi_float64_array + TypedArrayBigInt64Array TypedArrayType = C.napi_bigint64_array + TypedArrayBigUint64Array TypedArrayType = C.napi_biguint64_array +) diff --git a/internal/napi/types.go b/internal/napi/types.go new file mode 100644 index 0000000..a3e7183 --- /dev/null +++ b/internal/napi/types.go @@ -0,0 +1,41 @@ +package napi + +/* +#include +*/ +import "C" +import "unsafe" + +type PropertyDescriptor struct { + Utf8name string + Name Value + Method Callback + Getter Callback + Setter Callback + Value Value + Attributes PropertyAttributes + Data unsafe.Pointer +} + +type Finalize func(env Env, finalizeData, finalizeHint unsafe.Pointer) + +func FinalizeToFinalizer(fn Finalize) Finalizer { + return func(env C.napi_env, finalizeData, finalizeHint unsafe.Pointer) { + fn(Env(env), finalizeData, finalizeHint) + } +} + +// Finalizer as a C-compatible function pointer type +type Finalizer func(env C.napi_env, finalizeData, finalizeHint unsafe.Pointer) + +type Reference struct { + Ref unsafe.Pointer +} + +type EscapableHandleScope struct { + Scope unsafe.Pointer +} + +type HandleScope struct { + Scope unsafe.Pointer +} diff --git a/value.go b/internal/napi/value.go similarity index 100% rename from value.go rename to internal/napi/value.go diff --git a/internal/napi/value_type.go b/internal/napi/value_type.go new file mode 100644 index 0000000..81e8ebf --- /dev/null +++ b/internal/napi/value_type.go @@ -0,0 +1,48 @@ +package napi + +/* +#include +*/ +import "C" + +type ValueType C.napi_valuetype + +const ( + ValueTypeUndefined ValueType = C.napi_undefined + ValueTypeNull ValueType = C.napi_null + ValueTypeBoolean ValueType = C.napi_boolean + ValueTypeNumber ValueType = C.napi_number + ValueTypeString ValueType = C.napi_string + ValueTypeSymbol ValueType = C.napi_symbol + ValueTypeObject ValueType = C.napi_object + ValueTypeFunction ValueType = C.napi_function + ValueTypeExternal ValueType = C.napi_external + ValueTypeBigint ValueType = C.napi_bigint +) + +func (v ValueType) String() string { + switch v { + case ValueTypeUndefined: + return "undefined" + case ValueTypeNull: + return "null" + case ValueTypeBoolean: + return "boolean" + case ValueTypeNumber: + return "number" + case ValueTypeString: + return "string" + case ValueTypeSymbol: + return "symbol" + case ValueTypeObject: + return "object" + case ValueTypeFunction: + return "function" + case ValueTypeExternal: + return "external" + case ValueTypeBigint: + return "bigint" + default: + return "undefined" + } +} diff --git a/js/callback.go b/js/callback.go deleted file mode 100644 index 1cf487a..0000000 --- a/js/callback.go +++ /dev/null @@ -1,32 +0,0 @@ -package js - -import ( - "github.com/akshayganeshen/napi-go" -) - -type Callback = func(env Env, this Value, args []Value) any - -func AsCallback(fn Callback) napi.Callback { - return func(env napi.Env, info napi.CallbackInfo) napi.Value { - cbInfo, st := napi.GetCbInfo(env, info) - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - - jsEnv := AsEnv(env) - this := Value{ - Env: jsEnv, - Value: cbInfo.This, - } - args := make([]Value, len(cbInfo.Args)) - for i, cbArg := range cbInfo.Args { - args[i] = Value{ - Env: jsEnv, - Value: cbArg, - } - } - - result := fn(jsEnv, this, args) - return jsEnv.ValueOf(result).Value - } -} diff --git a/js/env.go b/js/env.go deleted file mode 100644 index 7e60fea..0000000 --- a/js/env.go +++ /dev/null @@ -1,196 +0,0 @@ -package js - -import ( - "fmt" - "unsafe" - - "github.com/akshayganeshen/napi-go" -) - -type Env struct { - Env napi.Env -} - -type InvalidValueTypeError struct { - Value any -} - -var _ error = InvalidValueTypeError{} - -func AsEnv(env napi.Env) Env { - return Env{ - Env: env, - } -} - -func (e Env) Global() Value { - v, st := napi.GetGlobal(e.Env) - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - return Value{ - Env: e, - Value: v, - } -} - -func (e Env) Null() Value { - v, st := napi.GetNull(e.Env) - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - return Value{ - Env: e, - Value: v, - } -} - -func (e Env) Undefined() Value { - v, st := napi.GetUndefined(e.Env) - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - return Value{ - Env: e, - Value: v, - } -} - -func (e Env) ValueOf(x any) Value { - var ( - v napi.Value - st napi.Status - ) - - switch xt := x.(type) { - case Value: - return xt - case []Value: - l := len(xt) - v, st = napi.CreateArrayWithLength(e.Env, l) - if st != napi.StatusOK { - break - } - - for i, xti := range xt { - // TODO: Use Value.SetIndex helper - st = napi.SetElement(e.Env, v, i, xti.Value) - if st != napi.StatusOK { - break - } - } - case Func: - return xt.Value - case Callback: - return e.FuncOf(xt).Value - case *Promise: - v, st = xt.Promise.Value, napi.StatusOK - case napi.Value: - v, st = xt, napi.StatusOK - - case nil: - v, st = napi.GetNull(e.Env) - case bool: - v, st = napi.GetBoolean(e.Env, xt) - case int: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case int8: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case int16: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case int64: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case uint: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case uint8: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case uint16: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case uint64: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case uintptr: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case unsafe.Pointer: - v, st = napi.CreateDouble(e.Env, float64(uintptr(xt))) - case float32: - v, st = napi.CreateDouble(e.Env, float64(xt)) - case float64: - v, st = napi.CreateDouble(e.Env, xt) - case string: - v, st = napi.CreateStringUtf8(e.Env, xt) - case error: - msg := e.ValueOf(xt.Error()) - v, st = napi.CreateError(e.Env, nil, msg.Value) - case []any: - l := len(xt) - v, st = napi.CreateArrayWithLength(e.Env, l) - if st != napi.StatusOK { - break - } - - for i, xti := range xt { - // TODO: Use Value.SetIndex helper - vti := e.ValueOf(xti) - st = napi.SetElement(e.Env, v, i, vti.Value) - if st != napi.StatusOK { - break - } - } - case map[string]any: - v, st = napi.CreateObject(e.Env) - if st != napi.StatusOK { - break - } - - for xtk, xtv := range xt { - // TODO: Use Value.Set helper - vtk, vtv := e.ValueOf(xtk), e.ValueOf(xtv) - st = napi.SetProperty(e.Env, v, vtk.Value, vtv.Value) - if st != napi.StatusOK { - break - } - } - - default: - panic(InvalidValueTypeError{x}) - } - - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - - return Value{ - Env: e, - Value: v, - } -} - -func (e Env) FuncOf(fn Callback) Func { - // TODO: Add CreateReference to FuncOf to keep value alive - v, st := napi.CreateFunction( - e.Env, - "", - AsCallback(fn), - ) - - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - - return Func{ - Value: Value{ - Env: e, - Value: v, - }, - } -} - -func (e Env) NewPromise() *Promise { - var result Promise - result.reset(e) - return &result -} - -func (err InvalidValueTypeError) Error() string { - return fmt.Sprintf("Value cannot be represented in JS: %T", err.Value) -} diff --git a/js/func.go b/js/func.go deleted file mode 100644 index 8da11dd..0000000 --- a/js/func.go +++ /dev/null @@ -1,5 +0,0 @@ -package js - -type Func struct { - Value -} diff --git a/js/funcof.md b/js/funcof.md new file mode 100644 index 0000000..9fc231e --- /dev/null +++ b/js/funcof.md @@ -0,0 +1,21 @@ +# Go functions conversion to Javascript functions + +## Simples conversions + +| Go | Node | +| --------------------------------- | ------------------------------------------- | +| `func()` | `function(): void` | +| `func() value` | `function(): value` | +| `func(value1, value2)` | `function(value1, value2): void` | +| `func() (value1, value2)` | `function(): [value1, value2]` | +| `func(value1, value2, values...)` | `function(value1, value2, ...values): void` | +| `func(values...)` | `function(...values): void` | + +## Error throw + +| Go | Node | +| ------------------------------------------ | --------------------------------------------------- | +| `func() error` | `function(): throw Error` | +| `func(value1, value2) error` | `function(value1, value2): throw Error` | +| `func() (value, error)` | `function(): value \|\| throw Error` | +| `func(value1, value2) (...nValues, error)` | `function(value1, value2): (Array \|\|throw Error)` | diff --git a/js/js.go b/js/js.go new file mode 100644 index 0000000..57bc872 --- /dev/null +++ b/js/js.go @@ -0,0 +1,575 @@ +package js + +import ( + "encoding" + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" + "time" + + "sirherobrine23.com.br/Sirherobrine23/napi-go" +) + +const propertiesTagName = "napi" + +// Convert go types to valid NAPI, if not conpatible return Undefined. +func ValueOf(env napi.EnvType, value any) (napiValue napi.ValueType, err error) { + return valueOf(env, reflect.ValueOf(value)) +} + +// Convert NAPI value to Go values +func ValueFrom(napiValue napi.ValueType, v any) error { + ptr := reflect.ValueOf(v) + if ptr.Kind() != reflect.Pointer { + return fmt.Errorf("require point to convert napi value to go value") + } + return valueFrom(napiValue, ptr.Elem()) +} + +func valueOf(env napi.EnvType, ptr reflect.Value) (napiValue napi.ValueType, err error) { + defer func(err *error) { + if err2 := recover(); err2 != nil { + switch v := err2.(type) { + case error: + *err = v + default: + *err = fmt.Errorf("panic recover: %s", err2) + } + } + }(&err) + + if !ptr.IsValid() || ptr.IsZero() { + return env.Undefined() + } + + // Marshalers + if ptr.CanInterface() { + switch v := ptr.Interface().(type) { + case napi.ValueType: + return v, nil + case time.Time: + return napi.CreateDate(env, v) + case encoding.TextMarshaler: + data, err := v.MarshalText() + if err != nil { + return nil, err + } + return napi.CreateString(env, string(data)) + case json.Marshaler: + var pointData any + data, err := v.MarshalJSON() + if err != nil { + return nil, err + } else if err = json.Unmarshal(data, &pointData); err != nil { + return nil, err + } + return ValueOf(env, pointData) + } + } + + ptrType := ptr.Type() + switch ptrType.Kind() { + case reflect.Pointer: + return valueOf(env, ptr.Elem()) + case reflect.String: + return napi.CreateString(env, ptr.String()) + case reflect.Bool: + return napi.CreateBoolean(env, ptr.Bool()) + case reflect.Int, reflect.Uint, reflect.Int32, reflect.Uint32, reflect.Float32, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16: + return napi.CreateNumber(env, ptr.Int()) + case reflect.Float64: + return napi.CreateNumber(env, ptr.Float()) + case reflect.Int64, reflect.Uint64: + return napi.CreateBigint(env, ptr.Int()) + case reflect.Func: + return funcOf(env, ptr) + case reflect.Slice, reflect.Array: + arr, err := napi.CreateArray(env, ptr.Len()) + if err != nil { + return nil, err + } + for index := range ptr.Len() { + value, err := valueOf(env, ptr.Index(index)) + if err != nil { + return arr, err + } else if err = arr.Set(index, value); err != nil { + return arr, err + } + } + return arr, nil + case reflect.Struct: + obj, err := napi.CreateObject(env) + if err != nil { + return nil, err + } + + for keyIndex := range ptrType.NumField() { + field, fieldType := ptr.Field(keyIndex), ptrType.Field(keyIndex) + if !fieldType.IsExported() || fieldType.Tag.Get(propertiesTagName) == "-" { + continue + } + + value, err := valueOf(env, field) + if err != nil { + return obj, err + } + + typeof, err := value.Type() + if err != nil { + return nil, err + } + + keyNamed := fieldType.Name + if strings.Count(fieldType.Tag.Get(propertiesTagName), ",") > 0 { + fields := strings.SplitN(fieldType.Tag.Get(propertiesTagName), ",", 2) + keyNamed = fields[0] + switch fields[1] { + case "omitempty": + switch typeof { + case napi.TypeUndefined, napi.TypeNull, napi.TypeUnkown: + continue + case napi.TypeString: + str, err := napi.ToString(value).Utf8Value() + if err != nil { + return nil, err + } else if str == "" { + continue + } + } + case "omitzero": + switch typeof { + case napi.TypeUndefined, napi.TypeNull, napi.TypeUnkown: + continue + case napi.TypeDate: + value, err := napi.ToDate(value).Time() + if err != nil { + return nil, err + } else if value.Unix() == 0 { + continue + } + case napi.TypeBigInt: + value, err := napi.ToBigint(value).Int64() + if err != nil { + return nil, err + } else if value == 0 { + continue + } + case napi.TypeNumber: + value, err := napi.ToNumber(value).Int() + if err != nil { + return nil, err + } else if value == 0 { + continue + } + case napi.TypeArray: + value, err := napi.ToArray(value).Length() + if err != nil { + return nil, err + } else if value == 0 { + continue + } + } + } + } + if err = obj.Set(keyNamed, value); err != nil { + return obj, err + } + } + + return obj, nil + case reflect.Map: + obj, err := napi.CreateObject(env) + if err != nil { + return nil, err + } + for ptrKey, ptrValue := range ptr.Seq2() { + key, err := valueOf(env, ptrKey) + if err != nil { + return nil, err + } + value, err := valueOf(env, ptrValue) + if err != nil { + return nil, err + } else if err = obj.SetWithValue(key, value); err != nil { + return nil, err + } + } + return obj, nil + case reflect.Interface: + if ptr.IsValid() { + if ptr.IsNil() { + return env.Null() + } else if ptr.CanInterface() { + return valueOf(env, reflect.ValueOf(ptr.Interface())) + } + } + } + return env.Undefined() +} + +func valueFrom(napiValue napi.ValueType, ptr reflect.Value) error { + typeOf, err := napiValue.Type() + if err != nil { + return err + } + + ptrType := ptr.Type() + switch ptrType.Kind() { + case reflect.Pointer: + return valueFrom(napiValue, ptr.Elem()) + case reflect.Interface: + // Check if is any and can set + if ptr.CanSet() && ptrType == reflect.TypeFor[any]() { + switch typeOf { + case napi.TypeNull, napi.TypeUndefined, napi.TypeUnkown: + ptr.Set(reflect.Zero(ptrType)) + return nil + case napi.TypeBoolean: + valueOf, err := napi.ToBoolean(napiValue).Value() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(valueOf)) + case napi.TypeNumber: + numberValue, err := napi.ToNumber(napiValue).Float() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(numberValue)) + case napi.TypeBigInt: + numberValue, err := napi.ToBigint(napiValue).Int64() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(numberValue)) + case napi.TypeString: + str, err := napi.ToString(napiValue).Utf8Value() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(str)) + case napi.TypeDate: + timeDate, err := napi.ToDate(napiValue).Time() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(timeDate)) + case napi.TypeArray: + napiArray := napi.ToArray(napiValue) + size, err := napiArray.Length() + if err != nil { + return err + } + value := reflect.MakeSlice(reflect.SliceOf(ptrType), size, size) + for index := range size { + napiValue, err := napiArray.Get(index) + if err != nil { + return err + } else if err = valueFrom(napiValue, value.Index(index)); err != nil { + return err + } + } + ptr.Set(value) + case napi.TypeBuffer: + buff, err := napi.ToBuffer(napiValue).Data() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(buff)) + case napi.TypeObject: + obj := napi.ToObject(napiValue) + goMap := reflect.MakeMap(reflect.MapOf(reflect.TypeFor[string](), reflect.TypeFor[any]())) + for keyName, value := range obj.Seq() { + valueOf := reflect.New(reflect.TypeFor[any]()) + if err := valueFrom(value, valueOf); err != nil { + return err + } + goMap.SetMapIndex(reflect.ValueOf(keyName), valueOf) + } + ptr.Set(goMap) + case napi.TypeFunction: + ptr.Set(reflect.ValueOf(napi.ToFunction(napiValue))) + } + return nil + } + return fmt.Errorf("cannot set value, returned %s", typeOf) + } + + switch typeOf { + case napi.TypeNull, napi.TypeUndefined, napi.TypeUnkown: + switch ptrType.Kind() { + case reflect.Interface, reflect.Pointer: + ptr.Set(reflect.Zero(ptrType)) + return nil + default: + return fmt.Errorf("cannot set value, returned %s", typeOf) + } + case napi.TypeBoolean: + switch ptr.Kind() { + case reflect.Bool: + valueOf, err := napi.ToBoolean(napiValue).Value() + if err != nil { + return err + } + ptr.SetBool(valueOf) + default: + return fmt.Errorf("cannot set boolean value to %s", ptr.Kind()) + } + case napi.TypeNumber: + switch ptrType.Kind() { + case reflect.Float32, reflect.Float64: + floatValue, err := napi.ToNumber(napiValue).Float() + if err != nil { + return err + } + ptr.SetFloat(floatValue) + return nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + numberValue, err := napi.ToNumber(napiValue).Int() + if err != nil { + return err + } + ptr.SetInt(numberValue) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + numberValue, err := napi.ToNumber(napiValue).Int() + if err != nil { + return err + } + ptr.SetUint(uint64(numberValue)) + return nil + default: + return fmt.Errorf("cannot set number value to %s", ptr.Kind()) + } + case napi.TypeBigInt: + switch ptrType.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + numberValue, err := napi.ToNumber(napiValue).Int() + if err != nil { + return err + } + ptr.SetInt(numberValue) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + numberValue, err := napi.ToNumber(napiValue).Int() + if err != nil { + return err + } + ptr.SetUint(uint64(numberValue)) + return nil + default: + return fmt.Errorf("cannot set number value to %s", ptr.Kind()) + } + case napi.TypeString: + switch ptr.Kind() { + case reflect.String: + default: + return fmt.Errorf("cannot set string to %s", ptr.Kind()) + } + str, err := napi.ToString(napiValue).Utf8Value() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(str)) + return nil + case napi.TypeDate: + switch ptrType.Kind() { + case reflect.Struct: + if ptrType == reflect.TypeFor[time.Time]() { + break + } + fallthrough + default: + return fmt.Errorf("cannot set Date to %s", ptr.Kind()) + } + timeDate, err := napi.ToDate(napiValue).Time() + if err != nil { + return err + } + ptr.Set(reflect.ValueOf(timeDate)) + return nil + case napi.TypeArray: + napiArray := napi.ToArray(napiValue) + size, err := napiArray.Length() + if err != nil { + return err + } + + switch ptr.Kind() { + case reflect.Slice: + value := reflect.MakeSlice(ptrType, size, size) + for index := range size { + napiValue, err := napiArray.Get(index) + if err != nil { + return err + } else if err = valueFrom(napiValue, value.Index(index)); err != nil { + return err + } + } + ptr.Set(value) + return nil + case reflect.Array: + value := reflect.New(ptrType) + for index := range min(size, value.Len()) { + napiValue, err := napiArray.Get(index) + if err != nil { + return err + } else if err = valueFrom(napiValue, value.Index(index)); err != nil { + return err + } + } + ptr.Set(value) + return nil + default: + return fmt.Errorf("cannot set Array to %s", ptr.Kind()) + } + case napi.TypeBuffer: + switch ptr.Kind() { + case reflect.Slice: + if ptrType == reflect.TypeFor[[]byte]() { + break + } + fallthrough + default: + return fmt.Errorf("cannot set Buffer to %s", ptr.Kind()) + } + buff, err := napi.ToBuffer(napiValue).Data() + if err != nil { + return err + } + ptr.SetBytes(buff) + return nil + case napi.TypeObject: + obj := napi.ToObject(napiValue) + switch ptr.Kind() { + case reflect.Struct: + ptr.Set(reflect.New(ptrType).Elem()) + for keyIndex := range ptrType.NumField() { + field, fieldType := ptr.Field(keyIndex), ptrType.Field(keyIndex) + if !fieldType.IsExported() || fieldType.Tag.Get(propertiesTagName) == "-" { + continue + } + + keyName, omitEmpty, omitZero := fieldType.Name, false, false + if strings.Count(fieldType.Tag.Get(propertiesTagName), ",") > 0 { + fields := strings.SplitN(fieldType.Tag.Get(propertiesTagName), ",", 2) + keyName = fields[0] + switch fields[1] { + case "omitempty": + omitEmpty = true + case "omitzero": + omitZero = true + } + } else { + omitEmpty, omitZero = true, true + } + + if ok, err := obj.Has(keyName); err != nil { + return err + } else if !ok && !(omitEmpty || omitZero) { + return fmt.Errorf("cannot set %s to %s", keyName, ptr.Kind()) + } + + value, err := obj.Get(keyName) + if err != nil { + return err + } + + valueTypeof, _ := value.Type() + if omitEmpty || omitZero { + switch valueTypeof { + case napi.TypeUndefined, napi.TypeNull, napi.TypeUnkown: + continue + case napi.TypeString: + if str, _ := napi.ToString(value).Utf8Value(); str == "" { + continue + } + case napi.TypeDate: + if timeDate, _ := napi.ToDate(value).Time(); timeDate.Unix() == 0 { + continue + } + case napi.TypeBigInt: + if numberValue, _ := napi.ToBigint(value).Int64(); numberValue == 0 { + continue + } + case napi.TypeNumber: + if numberValue, _ := napi.ToNumber(value).Int(); numberValue == 0 { + continue + } + case napi.TypeArray: + if size, _ := napi.ToArray(value).Length(); size == 0 { + continue + } + } + } + + valueOf := reflect.New(fieldType.Type).Elem() + if err := valueFrom(value, valueOf); err != nil { + return err + } + field.Set(valueOf) + } + return nil + case reflect.Map: + // Check if key is string, bool, int*, uint*, float*, else return error + switch ptrType.Key().Kind() { + case reflect.String: + case reflect.Bool: + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + case reflect.Float32, reflect.Float64: + default: + return fmt.Errorf("cannot set Object to %s", ptr.Kind()) + } + + goMap := reflect.MakeMap(ptrType) + for keyName, value := range obj.Seq() { + keySetValue := reflect.New(ptrType.Key()).Elem() + switch ptrType.Key().Kind() { + case reflect.String: + keySetValue.SetString(keyName) + case reflect.Bool: + boolV, err := strconv.ParseBool(keyName) + if err != nil { + return err + } + keySetValue.SetBool(boolV) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intV, err := strconv.ParseInt(keyName, 10, 64) + if err != nil { + return err + } + keySetValue.SetInt(intV) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + intV, err := strconv.ParseUint(keyName, 10, 64) + if err != nil { + return err + } + keySetValue.SetUint(intV) + case reflect.Float32, reflect.Float64: + floatV, err := strconv.ParseFloat(keyName, 64) + if err != nil { + return err + } + keySetValue.SetFloat(floatV) + } + + valueOf := reflect.New(ptrType.Elem()).Elem() + if err := valueFrom(value, valueOf); err != nil { + return err + } + goMap.SetMapIndex(keySetValue, valueOf) + } + ptr.Set(goMap) + return nil + default: + return fmt.Errorf("cannot set Object to %s", ptr.Kind()) + } + default: + println(typeOf.String()) + } + + return nil +} diff --git a/js/js_func.go b/js/js_func.go new file mode 100644 index 0000000..47968a3 --- /dev/null +++ b/js/js_func.go @@ -0,0 +1,123 @@ +package js + +import ( + "fmt" + "reflect" + "runtime" + + "sirherobrine23.com.br/Sirherobrine23/napi-go" + internalNapi "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +// Create napi bind to golang functions +func GoFuncOf(env napi.EnvType, function any) (napi.ValueType, error) { + return funcOf(env, reflect.ValueOf(function)) +} + +func funcOf(env napi.EnvType, ptr reflect.Value) (napi.ValueType, error) { + if ptr.Kind() != reflect.Func { + return nil, fmt.Errorf("return function to return napi value") + } + + funcName := runtime.FuncForPC(ptr.Pointer()).Name() + switch v := ptr.Interface().(type) { + case nil: + return nil, nil + case napi.Callback: + return napi.CreateFunction(env, funcName, v) + case internalNapi.Callback: + return napi.CreateFunctionNapi(env, funcName, v) + default: + return napi.CreateFunction(env, funcName, func(env napi.EnvType, this napi.ValueType, args []napi.ValueType) (napi.ValueType, error) { + fnType := ptr.Type() + returnValues := []reflect.Value{} + switch { + case fnType.NumIn() == 0 && fnType.NumOut() == 0: + returnValues = ptr.Call([]reflect.Value{}) + case !fnType.IsVariadic(): + returnValues = ptr.Call(goValuesInFunc(ptr, args, false)) + default: + returnValues = ptr.CallSlice(goValuesInFunc(ptr, args, true)) + } + + switch len(returnValues) { + case 0: + return nil, nil + case 1: + return valueOf(env, returnValues[0]) + default: + lastValue := returnValues[len(returnValues)-1] + if lastValue.CanConvert(reflect.TypeFor[error]()) { + returnValues = returnValues[:len(returnValues)-1] + if !lastValue.IsNil() { + if err := lastValue.Interface().(error); err != nil { + return nil, err + } + } + + } + + switch len(returnValues) { + case 1: + // Return value from return + return valueOf(env, returnValues[0]) + default: + // Create array + arr, err := napi.CreateArray(env, len(returnValues)) + if err != nil { + return nil, err + } + + // Append values to js array + for index, value := range returnValues { + napiValue, err := valueOf(env, value) + if err != nil { + return nil, err + } else if err = arr.Set(index, napiValue); err != nil { + return nil, err + } + } + + return arr, nil + } + } + }) + } +} + +func goValuesInFunc(ptr reflect.Value, jsArgs []napi.ValueType, variadic bool) (values []reflect.Value) { + if variadic && (ptr.Type().NumIn()-1 > 0) && ptr.Type().NumIn()-1 < len(jsArgs) { + panic(fmt.Errorf("require minimun %d arguments, called with %d", ptr.Type().NumIn()-1, len(jsArgs))) + } else if !variadic &&ptr.Type().NumIn() != len(jsArgs) { + panic(fmt.Errorf("require %d arguments, called with %d", ptr.Type().NumIn(), len(jsArgs))) + } + + size := ptr.Type().NumIn() + if variadic { + size-- + } + + values = make([]reflect.Value, size) + for index := range values { + valueOf := reflect.New(ptr.Type().In(index)) + if err := valueFrom(jsArgs[index], valueOf); err != nil { + panic(err) + } + values[index] = valueOf + } + + if variadic { + variadicType := ptr.Type().In(size).Elem() + + valueAppend := jsArgs[size:] + valueOf := reflect.MakeSlice(reflect.SliceOf(variadicType), len(valueAppend), len(valueAppend)) + for index := range valueAppend { + if err := valueFrom(valueAppend[index], valueOf.Index(index)); err != nil { + panic(err) + } + } + values = append(values, valueOf) + } + + return +} diff --git a/js/promise.go b/js/promise.go deleted file mode 100644 index 20bcb9f..0000000 --- a/js/promise.go +++ /dev/null @@ -1,118 +0,0 @@ -package js - -import ( - "errors" - - "github.com/akshayganeshen/napi-go" -) - -type Promise struct { - Promise napi.Promise - ThreadsafeFunction napi.ThreadsafeFunction - Result any - ResultType PromiseResultType -} - -type PromiseResultType string - -type PromiseProvider interface { - Resolve(resolution any) - Reject(rejection any) -} - -var _ PromiseProvider = &Promise{} - -const ( - PromiseResultTypeResolved PromiseResultType = "resolved" - PromiseResultTypeRejected PromiseResultType = "rejected" -) - -var ErrPromiseSettled = errors.New( - "Promise: Cannot resolve/reject a settled promise", -) - -func (p *Promise) Resolve(resolution any) { - p.ensurePending() - - p.Result = resolution - p.ResultType = PromiseResultTypeResolved - - // function has already been acquired during reset - defer p.release() - p.settle() -} - -func (p *Promise) Reject(rejection any) { - p.ensurePending() - - p.Result = rejection - p.ResultType = PromiseResultTypeRejected - - // function has already been acquired during reset - defer p.release() - p.settle() -} - -func (p *Promise) reset(e Env) { - np, st := napi.CreatePromise(e.Env) - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - - asyncResourceName := e.ValueOf("napi-go/js-promise") - fn := e.FuncOf(func(env Env, this Value, args []Value) any { - value := env.ValueOf(p.Result) - - st := napi.StatusOK - switch p.ResultType { - case PromiseResultTypeResolved: - st = napi.ResolveDeferred(env.Env, p.Promise.Deferred, value.Value) - case PromiseResultTypeRejected: - st = napi.RejectDeferred(env.Env, p.Promise.Deferred, value.Value) - } - - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - - return nil - }) - - tsFn, st := napi.CreateThreadsafeFunction( - e.Env, - fn.Value.Value, - nil, asyncResourceName.Value, - 0, - 1, // initialize with 1 acquisition - ) - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } - - *p = Promise{ - Promise: np, - ThreadsafeFunction: tsFn, - } -} - -func (p *Promise) ensurePending() { - if p.ResultType != "" { - panic(ErrPromiseSettled) - } -} - -func (p *Promise) settle() { - st := napi.CallThreadsafeFunction(p.ThreadsafeFunction) - if st != napi.StatusOK { - panic(napi.StatusError(st)) - } -} - -func (p *Promise) release() { - st := napi.ReleaseThreadsafeFunction(p.ThreadsafeFunction) - if st == napi.StatusClosing { - p.ThreadsafeFunction = nil - } else if st != napi.StatusOK { - panic(napi.StatusError(st)) - } -} diff --git a/js/value.go b/js/value.go deleted file mode 100644 index 71fdcef..0000000 --- a/js/value.go +++ /dev/null @@ -1,14 +0,0 @@ -package js - -import ( - "github.com/akshayganeshen/napi-go" -) - -type Value struct { - Env Env - Value napi.Value -} - -func (v Value) GetEnv() Env { - return v.Env -} diff --git a/js_native_api.go b/js_native_api.go deleted file mode 100644 index 3bcd6dc..0000000 --- a/js_native_api.go +++ /dev/null @@ -1,341 +0,0 @@ -package napi - -/* -#include -#include -*/ -import "C" - -import ( - "unsafe" -) - -func GetUndefined(env Env) (Value, Status) { - var result Value - status := Status(C.napi_get_undefined( - C.napi_env(env), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func GetNull(env Env) (Value, Status) { - var result Value - status := Status(C.napi_get_null( - C.napi_env(env), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func GetGlobal(env Env) (Value, Status) { - var result Value - status := Status(C.napi_get_global( - C.napi_env(env), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func GetBoolean(env Env, value bool) (Value, Status) { - var result Value - status := Status(C.napi_get_boolean( - C.napi_env(env), - C.bool(value), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func CreateObject(env Env) (Value, Status) { - var result Value - status := Status(C.napi_create_object( - C.napi_env(env), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func CreateArray(env Env) (Value, Status) { - var result Value - status := Status(C.napi_create_array( - C.napi_env(env), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func CreateArrayWithLength(env Env, length int) (Value, Status) { - var result Value - status := Status(C.napi_create_array_with_length( - C.napi_env(env), - C.size_t(length), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func CreateDouble(env Env, value float64) (Value, Status) { - var result Value - status := Status(C.napi_create_double( - C.napi_env(env), - C.double(value), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func CreateStringUtf8(env Env, str string) (Value, Status) { - cstr := C.CString(str) - defer C.free(unsafe.Pointer(cstr)) - - var result Value - status := Status(C.napi_create_string_utf8( - C.napi_env(env), - cstr, - C.size_t(len([]byte(str))), // must pass number of bytes - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func CreateSymbol(env Env, description Value) (Value, Status) { - var result Value - status := Status(C.napi_create_symbol( - C.napi_env(env), - C.napi_value(description), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func CreateFunction(env Env, name string, cb Callback) (Value, Status) { - provider, status := getInstanceData(env) - if status != StatusOK || provider == nil { - return nil, status - } - - return provider.GetCallbackData().CreateCallback(env, name, cb) -} - -func CreateError(env Env, code, msg Value) (Value, Status) { - var result Value - status := Status(C.napi_create_error( - C.napi_env(env), - C.napi_value(code), - C.napi_value(msg), - (*C.napi_value)(unsafe.Pointer(&result)), - )) - return result, status -} - -func Typeof(env Env, value Value) (ValueType, Status) { - var result ValueType - status := Status(C.napi_typeof( - C.napi_env(env), - C.napi_value(value), - (*C.napi_valuetype)(unsafe.Pointer(&result)), - )) - return result, status -} - -func GetValueDouble(env Env, value Value) (float64, Status) { - var result float64 - status := Status(C.napi_get_value_double( - C.napi_env(env), - C.napi_value(value), - (*C.double)(unsafe.Pointer(&result)), - )) - return result, status -} - -func GetValueBool(env Env, value Value) (bool, Status) { - var result bool - status := Status(C.napi_get_value_bool( - C.napi_env(env), - C.napi_value(value), - (*C.bool)(unsafe.Pointer(&result)), - )) - return result, status -} - -func GetValueStringUtf8(env Env, value Value) (string, Status) { - // call napi_get_value_string_utf8 twice - // first is to get number of bytes - // second is to populate the actual string buffer - bufsize := C.size_t(0) - var strsize C.size_t - - status := Status(C.napi_get_value_string_utf8( - C.napi_env(env), - C.napi_value(value), - nil, - bufsize, - &strsize, - )) - - if status != StatusOK { - return "", status - } - - // ensure there is room for the null terminator as well - strsize++ - cstr := (*C.char)(C.malloc(C.sizeof_char * strsize)) - defer C.free(unsafe.Pointer(cstr)) - - status = Status(C.napi_get_value_string_utf8( - C.napi_env(env), - C.napi_value(value), - cstr, - strsize, - &strsize, - )) - - if status != StatusOK { - return "", status - } - - return C.GoStringN( - (*C.char)(cstr), - (C.int)(strsize), - ), status -} - -func SetProperty(env Env, object, key, value Value) Status { - return Status(C.napi_set_property( - C.napi_env(env), - C.napi_value(object), - C.napi_value(key), - C.napi_value(value), - )) -} - -func SetElement(env Env, object Value, index int, value Value) Status { - return Status(C.napi_set_element( - C.napi_env(env), - C.napi_value(object), - C.uint32_t(index), - C.napi_value(value), - )) -} - -func StrictEquals(env Env, lhs, rhs Value) (bool, Status) { - var result bool - status := Status(C.napi_strict_equals( - C.napi_env(env), - C.napi_value(lhs), - C.napi_value(rhs), - (*C.bool)(&result), - )) - return result, status -} - -type GetCbInfoResult struct { - Args []Value - This Value -} - -func GetCbInfo(env Env, info CallbackInfo) (GetCbInfoResult, Status) { - // call napi_get_cb_info twice - // first is to get total number of arguments - // second is to populate the actual arguments - argc := C.size_t(0) - status := Status(C.napi_get_cb_info( - C.napi_env(env), - C.napi_callback_info(info), - &argc, - nil, - nil, - nil, - )) - - if status != StatusOK { - return GetCbInfoResult{}, status - } - - argv := make([]Value, int(argc)) - var cArgv unsafe.Pointer - if argc > 0 { - cArgv = unsafe.Pointer(&argv[0]) // must pass element pointer - } - - var thisArg Value - - status = Status(C.napi_get_cb_info( - C.napi_env(env), - C.napi_callback_info(info), - &argc, - (*C.napi_value)(cArgv), - (*C.napi_value)(unsafe.Pointer(&thisArg)), - nil, - )) - - return GetCbInfoResult{ - Args: argv, - This: thisArg, - }, status -} - -func Throw(env Env, err Value) Status { - return Status(C.napi_throw( - C.napi_env(env), - C.napi_value(err), - )) -} - -func ThrowError(env Env, code, msg string) Status { - codeCStr, msgCCstr := C.CString(code), C.CString(msg) - defer C.free(unsafe.Pointer(codeCStr)) - defer C.free(unsafe.Pointer(msgCCstr)) - - return Status(C.napi_throw_error( - C.napi_env(env), - codeCStr, - msgCCstr, - )) -} - -func CreatePromise(env Env) (Promise, Status) { - var result Promise - status := Status(C.napi_create_promise( - C.napi_env(env), - (*C.napi_deferred)(unsafe.Pointer(&result.Deferred)), - (*C.napi_value)(unsafe.Pointer(&result.Value)), - )) - return result, status -} - -func ResolveDeferred(env Env, deferred Deferred, resolution Value) Status { - return Status(C.napi_resolve_deferred( - C.napi_env(env), - C.napi_deferred(deferred), - C.napi_value(resolution), - )) -} - -func RejectDeferred(env Env, deferred Deferred, rejection Value) Status { - return Status(C.napi_reject_deferred( - C.napi_env(env), - C.napi_deferred(deferred), - C.napi_value(rejection), - )) -} - -func SetInstanceData(env Env, data any) Status { - provider, status := getInstanceData(env) - if status != StatusOK || provider == nil { - return status - } - - provider.SetUserData(data) - return status -} - -func GetInstanceData(env Env) (any, Status) { - provider, status := getInstanceData(env) - if status != StatusOK || provider == nil { - return nil, status - } - - return provider.GetUserData(), status -} diff --git a/makefile b/makefile deleted file mode 100644 index 3ef79fe..0000000 --- a/makefile +++ /dev/null @@ -1,40 +0,0 @@ -all: doc -clean: clean-doc - -EXAMPLE_DIR = docs/examples -EXAMPLE_PACKAGES = \ - async-promise \ - callback \ - describe-args \ - hello-world \ - js - -NAPI_LIB_SUFFIX = .node - -TARGET_BUILDDIR = build - -EXAMPLE_BINDINGS = $(addsuffix $(NAPI_LIB_SUFFIX),$(EXAMPLE_PACKAGES)) -TARGET_EXAMPLES = \ - $(addprefix $(TARGET_BUILDDIR)/, $(EXAMPLE_BINDINGS)) - -# TODO: Configure CGO_LDFLAGS_ALLOW for non-darwin systems. -CGO_LDFLAGS_ALLOW = (-Wl,(-undefined,dynamic_lookup|-no_pie|-search_paths_first)) - -doc: $(TARGET_EXAMPLES) - -$(TARGET_EXAMPLES): | $(TARGET_BUILDDIR) -$(TARGET_EXAMPLES): $(TARGET_BUILDDIR)/%$(NAPI_LIB_SUFFIX): $(EXAMPLE_DIR)/% - CGO_LDFLAGS_ALLOW='$(CGO_LDFLAGS_ALLOW)' \ - go build -buildmode=c-shared -o "$(@)" "./$(<)/" - -$(TARGET_BUILDDIR): - mkdir -p "$(TARGET_BUILDDIR)" - -clean: - rmdir "$(TARGET_BUILDDIR)" - -clean-doc: - rm -f $(patsubst %,"%",$(TARGET_EXAMPLES)) - -.PHONY: all doc -.PHONY: clean clean-doc diff --git a/napi_env.go b/napi_env.go new file mode 100644 index 0000000..02afc91 --- /dev/null +++ b/napi_env.go @@ -0,0 +1,50 @@ +package napi + +import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" + +type EnvType interface { + NapiValue() napi.Env // Primitive value to NAPI call + Global() (*Object, error) + Undefined() (ValueType, error) + Null() (ValueType, error) +} + +// Return N-API env reference +func N_APIEnv(env napi.Env) EnvType { return &Env{env} } + +// N-API Env +type Env struct{ + NapiEnv napi.Env +} + +// Return [napi.Env] to point from internal napi cgo +func (e *Env) NapiValue() napi.Env { + return e.NapiEnv +} + +// Return representantion to 'This' [*Object] +func (e *Env) Global() (*Object, error) { + napiValue, err := mustValueErr(napi.GetGlobal(e.NapiEnv)) + if err != nil { + return nil, err + } + return ToObject(N_APIValue(e, napiValue)), nil +} + +// Return Undefined value +func (e *Env) Undefined() (ValueType, error) { + napiValue, err := mustValueErr(napi.GetUndefined(e.NapiEnv)) + if err != nil { + return nil, err + } + return N_APIValue(e, napiValue), nil +} + +// Return Null value +func (e *Env) Null() (ValueType, error) { + napiValue, err := mustValueErr(napi.GetNull(e.NapiEnv)) + if err != nil { + return nil, err + } + return N_APIValue(e, napiValue), nil +} diff --git a/napi_status.go b/napi_status.go new file mode 100644 index 0000000..bccfc16 --- /dev/null +++ b/napi_status.go @@ -0,0 +1,35 @@ +package napi + +import "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" + +// Process status to return error if StatusOK return nil on error +func mustValueErr[T any](input T, status napi.Status) (T, error) { + if status != napi.StatusOK { + return input, napi.StatusError(status) + } + return input, nil +} + +// return error from status +func singleMustValueErr(status napi.Status) error { + if status != napi.StatusOK { + return napi.StatusError(status) + } + return nil +} + +// Process status to return error if StatusOK return nil on error +func mustValueErr2[T any](input T, _ bool, status napi.Status) (T, error) { + if status != napi.StatusOK { + return input, napi.StatusError(status) + } + return input, nil +} + +// Process status to return error if StatusOK return nil on error +func mustValueErr3[T, C any](input T, i2 C, status napi.Status) (T, C, error) { + if status != napi.StatusOK { + return input, i2, napi.StatusError(status) + } + return input, i2, nil +} diff --git a/napi_value.go b/napi_value.go new file mode 100644 index 0000000..c3d42a9 --- /dev/null +++ b/napi_value.go @@ -0,0 +1,167 @@ +package napi + +import ( + "fmt" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type ValueType interface { + NapiValue() napi.Value // Primitive value to NAPI call + NapiEnv() napi.Env // NAPI Env to NAPI call + + Env() EnvType // NAPI Env to NAPI call + Type() (NapiType, error) // NAPI Type of value +} + +type ( + value = ValueType // to dont expose to external structs + + // Generic type to NAPI value + Value struct { + env EnvType + valueOf napi.Value + } + + NapiType int // Return typeof of Value +) + +const ( + TypeUnkown NapiType = iota + TypeUndefined + TypeNull + TypeBoolean + TypeNumber + TypeBigInt + TypeString + TypeSymbol + TypeObject + TypeFunction + TypeExternal + TypeTypedArray + TypePromise + TypeDataView + TypeBuffer + TypeDate + TypeArray + TypeArrayBuffer + TypeError +) + +var napiTypeNames = map[NapiType]string{ + TypeUnkown: "Unknown", + TypeUndefined: "Undefined", + TypeNull: "Null", + TypeBoolean: "Boolean", + TypeNumber: "Number", + TypeBigInt: "BigInt", + TypeString: "String", + TypeSymbol: "Symbol", + TypeObject: "Object", + TypeFunction: "Function", + TypeExternal: "External", + TypeTypedArray: "TypedArray", + TypePromise: "Promise", + TypeDataView: "DaraView", + TypeBuffer: "Buffer", + TypeDate: "Date", + TypeArray: "Array", + TypeArrayBuffer: "ArrayBuffer", + TypeError: "Error", +} + +// Return [ValueType] from [napi.Value] +func N_APIValue(env EnvType, value napi.Value) ValueType { + return &Value{env: env, valueOf: value} +} + +func (v *Value) NapiValue() napi.Value { return v.valueOf } +func (v *Value) NapiEnv() napi.Env { return v.env.NapiValue() } +func (v *Value) Env() EnvType { return v.env } + +func (v *Value) Type() (NapiType, error) { + isTypedArray, err := mustValueErr(napi.IsTypedArray(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isPromise, err := mustValueErr(napi.IsPromise(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isDataView, err := mustValueErr(napi.IsDataView(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isBuffer, err := mustValueErr(napi.IsBuffer(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isDate, err := mustValueErr(napi.IsDate(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isArray, err := mustValueErr(napi.IsArray(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isArrayBuffer, err := mustValueErr(napi.IsArrayBuffer(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isError, err := mustValueErr(napi.IsError(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + isTypeof, err := mustValueErr(napi.Typeof(v.NapiEnv(), v.NapiValue())) + if err != nil { + return TypeUnkown, err + } + + switch { + case isTypedArray: + return TypeTypedArray, nil + case isPromise: + return TypePromise, nil + case isDataView: + return TypeDataView, nil + case isBuffer: + return TypeBuffer, nil + case isDate: + return TypeDate, nil + case isArray: + return TypeArray, nil + case isArrayBuffer: + return TypeArrayBuffer, nil + case isError: + return TypeError, nil + case isTypeof == napi.ValueTypeUndefined: + return TypeUndefined, nil + case isTypeof == napi.ValueTypeNull: + return TypeNull, nil + case isTypeof == napi.ValueTypeBoolean: + return TypeBoolean, nil + case isTypeof == napi.ValueTypeNumber: + return TypeNumber, nil + case isTypeof == napi.ValueTypeString: + return TypeString, nil + case isTypeof == napi.ValueTypeSymbol: + return TypeSymbol, nil + case isTypeof == napi.ValueTypeObject: + return TypeObject, nil + case isTypeof == napi.ValueTypeFunction: + return TypeFunction, nil + case isTypeof == napi.ValueTypeExternal: + return TypeExternal, nil + case isTypeof == napi.ValueTypeBigint: + return TypeBigInt, nil + } + + return TypeUnkown, nil +} + +func (t NapiType) String() string { + if name, ok := napiTypeNames[t]; ok { + return name + } + return fmt.Sprintf("Unknown NapiType %d", t) +} diff --git a/number.go b/number.go new file mode 100644 index 0000000..bd41a63 --- /dev/null +++ b/number.go @@ -0,0 +1,114 @@ +package napi + +import ( + "fmt" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type Number struct{ value } +type Bigint struct{ value } + +// Convert [ValueType] to [*Number] +func ToNumber(o ValueType) *Number { return &Number{o} } + +// Convert [ValueType] to [*Bigint] +func ToBigint(o ValueType) *Bigint { return &Bigint{o} } + +func (num *Number) Float() (float64, error) { + return mustValueErr(napi.GetValueDouble(num.NapiEnv(), num.NapiValue())) +} + +func (num *Number) Int() (int64, error) { + return mustValueErr(napi.GetValueInt64(num.NapiEnv(), num.NapiValue())) +} + +func (num *Number) Uint32() (uint32, error) { + return mustValueErr(napi.GetValueUint32(num.NapiEnv(), num.NapiValue())) +} + +func (num *Number) Int32() (int32, error) { + return mustValueErr(napi.GetValueInt32(num.NapiEnv(), num.NapiValue())) +} + +func (big *Bigint) Int64() (int64, error) { + return mustValueErr2(napi.GetValueBigIntInt64(big.NapiEnv(), big.NapiValue())) +} +func (big *Bigint) Uint64() (uint64, error) { + return mustValueErr2(napi.GetValueBigIntUint64(big.NapiEnv(), big.NapiValue())) +} + +func CreateBigint[T int64 | uint64](env EnvType, valueOf T) (*Bigint, error) { + var value napi.Value + var err error + switch v := any(valueOf).(type) { + case int64: + if value, err = mustValueErr(napi.CreateBigIntInt64(env.NapiValue(), v)); err != nil { + return nil, err + } + case uint64: + if value, err = mustValueErr(napi.CreateBigIntUint64(env.NapiValue(), v)); err != nil { + return nil, err + } + } + + return &Bigint{value: &Value{env: env, valueOf: value}}, nil +} + +func CreateNumber[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](env EnvType, n T) (*Number, error) { + var value napi.Value + var err error + switch v := any(n).(type) { + case int: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil { + return nil, err + } + case uint: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil { + return nil, err + } + case int8: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil { + return nil, err + } + case uint8: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil { + return nil, err + } + case int16: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil { + return nil, err + } + case uint16: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil { + return nil, err + } + case int32: + if value, err = mustValueErr(napi.CreateInt32(env.NapiValue(), v)); err != nil { + return nil, err + } + case uint32: + if value, err = mustValueErr(napi.CreateUint32(env.NapiValue(), v)); err != nil { + return nil, err + } + case int64: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), v)); err != nil { + return nil, err + } + case uint64: + if value, err = mustValueErr(napi.CreateInt64(env.NapiValue(), int64(v))); err != nil { + return nil, err + } + case float32: + if value, err = mustValueErr(napi.CreateDouble(env.NapiValue(), float64(v))); err != nil { + return nil, err + } + case float64: + if value, err = mustValueErr(napi.CreateDouble(env.NapiValue(), v)); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("invalid number type") + } + return ToNumber(N_APIValue(env, value)), err +} diff --git a/object.go b/object.go new file mode 100644 index 0000000..a0c0847 --- /dev/null +++ b/object.go @@ -0,0 +1,134 @@ +package napi + +import ( + "iter" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type Object struct{ value } + +// Convert ValueType to [*Object] +func ToObject(o ValueType) *Object { return &Object{o} } + +// Create [*Object] +func CreateObject(env EnvType) (*Object, error) { + napiValue, err := mustValueErr(napi.CreateObject(env.NapiValue())) + if err != nil { + return nil, err + } + return ToObject(N_APIValue(env, napiValue)), nil +} + +// Check if exists named property. +func (obj *Object) Has(name string) (bool, error) { + return mustValueErr(napi.HasNamedProperty(obj.NapiEnv(), obj.NapiValue(), name)) +} + +// Checks whether a own property is present. +func (obj *Object) HasOwnProperty(key ValueType) (bool, error) { + return mustValueErr(napi.HasOwnProperty(obj.NapiEnv(), obj.NapiValue(), key.NapiValue())) +} + +// Checks whether a own property is present. +func (obj *Object) HasOwnPropertyString(keyString string) (bool, error) { + napiString, err := CreateString(obj.Env(), keyString) + if err != nil { + return false, err + } + return obj.HasOwnProperty(napiString) +} + +// Gets a property. +func (obj *Object) Get(key string) (ValueType, error) { + keyValue, err := CreateString(obj.Env(), key) + if err != nil { + return nil, err + } + return obj.GetWithValue(keyValue) +} + +// Gets a property. +func (obj *Object) GetWithValue(key ValueType) (ValueType, error) { + napiValue, err := mustValueErr(napi.GetProperty(obj.Env().NapiValue(), obj.NapiValue(), key.NapiValue())) + if err != nil { + return nil, err + } + return N_APIValue(obj.Env(), napiValue), nil +} + +// Sets a property. +func (obj *Object) Set(key string, value ValueType) error { + keyValue, err := CreateString(obj.Env(), key) + if err != nil { + return err + } + return obj.SetWithValue(keyValue, value) +} + +// Sets a property. +func (obj *Object) SetWithValue(key, value ValueType) error { + return singleMustValueErr(napi.SetProperty(obj.NapiEnv(), obj.NapiValue(), key.NapiValue(), value.NapiValue())) +} + +// Delete property. +func (obj *Object) Delete(key string) (bool, error) { + keyValue, err := CreateString(obj.Env(), key) + if err != nil { + return false, err + } + return obj.DeleteWithValue(keyValue) +} + +// Delete property. +func (obj *Object) DeleteWithValue(key ValueType) (bool, error) { + return mustValueErr(napi.DeleteProperty(obj.NapiEnv(), obj.NapiValue(), key.NapiValue())) +} + +// Get all property names. +func (obj *Object) GetPropertyNames() (*Array, error) { + arrValue, err := mustValueErr(napi.GetPropertyNames(obj.NapiEnv(), obj.NapiValue())) + if err != nil { + return nil, err + } + return ToArray(N_APIValue(obj.Env(), arrValue)), nil +} + +// Checks if an object is an instance created by a constructor function, +// this is equivalent to the JavaScript `instanceof` operator. +func (obj *Object) InstanceOf(value ValueType) (bool, error) { + return mustValueErr(napi.InstanceOf(obj.NapiEnv(), obj.NapiValue(), value.NapiValue())) +} + +// Freeze object. +func (obj *Object) Freeze() error { + return singleMustValueErr(napi.ObjectFreeze(obj.NapiEnv(), obj.NapiValue())) +} + +func (obj *Object) Seal() error { + return singleMustValueErr(napi.ObjectSeal(obj.NapiEnv(), obj.NapiValue())) +} + +func (obj *Object) Seq() iter.Seq2[string, ValueType] { + keys, err := obj.GetPropertyNames() + if err != nil { + panic(err) + } + return func(yield func(string, ValueType) bool) { + for key := range keys.Seq() { + value, err := obj.GetWithValue(key) + if err != nil { + panic(err) + } + + keyName, err := ToString(key).Utf8Value() + if err != nil { + panic(err) + } + + if !yield(keyName, value) { + return + } + } + } +} diff --git a/string.go b/string.go new file mode 100644 index 0000000..51b6e7e --- /dev/null +++ b/string.go @@ -0,0 +1,44 @@ +package napi + +import ( + "unicode/utf16" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type String struct{ value } + +// Convert [ValueType] to [*String] +func ToString(o ValueType) *String { return &String{o} } + +// Create [*String] from go string +func CreateString(env EnvType, str string) (*String, error) { + napiString, err := mustValueErr(napi.CreateStringUtf8(env.NapiValue(), str)) + if err != nil { + return nil, err + } + return ToString(N_APIValue(env, napiString)), nil +} + +// Create string to utf16 +func CreateStringUtf16(env EnvType, str []rune) (*String, error) { + napiString, err := mustValueErr(napi.CreateStringUtf16(env.NapiValue(), utf16.Encode(str))) + if err != nil { + return nil, err + } + return ToString(N_APIValue(env, napiString)), nil +} + +// Get String value. +func (str *String) Utf8Value() (string, error) { + return mustValueErr(napi.GetValueStringUtf8(str.NapiEnv(), str.NapiValue())) +} + +// Converts a String value to a UTF-16 encoded in rune. +func (str *String) Utf16Value() ([]rune, error) { + valueOf, err := mustValueErr(napi.GetValueStringUtf16(str.NapiEnv(), str.NapiValue())) + if err != nil { + return nil, err + } + return utf16.Decode(valueOf), nil +} \ No newline at end of file diff --git a/value_type.go b/value_type.go deleted file mode 100644 index 3afe0ce..0000000 --- a/value_type.go +++ /dev/null @@ -1,21 +0,0 @@ -package napi - -/* -#include -*/ -import "C" - -type ValueType int - -const ( - ValueTypeUndefined ValueType = C.napi_undefined - ValueTypeNull ValueType = C.napi_null - ValueTypeBoolean ValueType = C.napi_boolean - ValueTypeNumber ValueType = C.napi_number - ValueTypeString ValueType = C.napi_string - ValueTypeSymbol ValueType = C.napi_symbol - ValueTypeObject ValueType = C.napi_object - ValueTypeFunction ValueType = C.napi_function - ValueTypeExternal ValueType = C.napi_external - ValueTypeBigint ValueType = C.napi_bigint -)