inline Napi::String StringFromUtf8(Napi::Env env, const char* data, int length) { if (length < 0) return Napi::String::New(env, data); return Napi::String::New(env, data, length); } // Node-API has no equivalent of V8's internalized strings, so these are simple // aliases of StringFromUtf8; the names are kept to preserve the intent of call // sites (strings that are used repeatedly as property keys). If Node-API ever // gains this functionality, we can easily restore it here. inline Napi::String InternalizedFromUtf8(Napi::Env env, const char* data, int length) { return StringFromUtf8(env, data, length); } inline Napi::Value InternalizedFromUtf8OrNull(Napi::Env env, const char* data, int length) { if (data == NULL) return env.Null(); return InternalizedFromUtf8(env, data, length); } inline Napi::String InternalizedFromLatin1(Napi::Env env, const char* str) { return Napi::String::New(env, str); } // Replicates the semantics of v8::Value::IsInt32() (an integral number within // the range of a 32-bit signed integer, excluding -0), which has no direct // Node-API equivalent. inline bool IsInt32(Napi::Value value) { if (!value.IsNumber()) return false; double num = value.As().DoubleValue(); if (!(num >= INT_MIN && num <= INT_MAX)) return false; if (num == 0) return !std::signbit(num); return static_cast(static_cast(num)) == num; } inline void SetFrozen(Napi::Env env, Napi::Object obj, const Napi::Reference& key, Napi::Value value) { obj.DefineProperty(Napi::PropertyDescriptor::Value(key.Value(), value, napi_enumerable)); } // The following helpers perform JavaScript operations that can execute // arbitrary user code (functions, proxies, getters). If the user code throws, // the exception is left pending in the environment and an empty value is // returned. The node-addon-api equivalents (Napi::Function::Call, // Napi::Object::Get, etc.) must NOT be used for such operations, because their // failure paths re-throw the pending exception through Napi::Error, which // aborts the process when the thrown value is null or undefined (a // node-addon-api bug, present as of v8.9.0, triggered by Node-API version 10). inline Napi::Value SafeCall(Napi::Env env, Napi::Function fn, napi_value recv, size_t argc, const napi_value* args) { napi_value result; if (napi_call_function(env, recv, fn, argc, args, &result) != napi_ok) return Napi::Value(); return Napi::Value(env, result); } inline Napi::Object SafeConstruct(Napi::Env env, Napi::Function constructor) { napi_value result; if (napi_new_instance(env, constructor, 0, NULL, &result) != napi_ok) return Napi::Object(); return Napi::Object(env, result); } inline Napi::Value SafeGet(Napi::Env env, Napi::Object obj, napi_value key) { napi_value result; if (napi_get_property(env, obj, key, &result) != napi_ok) return Napi::Value(); return Napi::Value(env, result); } inline Napi::Value SafeGetElement(Napi::Env env, Napi::Object obj, uint32_t index) { napi_value result; if (napi_get_element(env, obj, index, &result) != napi_ok) return Napi::Value(); return Napi::Value(env, result); } // Returns false if the check itself failed (leaving an exception pending). inline bool SafeHasOwnProperty(Napi::Env env, Napi::Object obj, napi_value key, bool* result) { return napi_has_own_property(env, obj, key, result) == napi_ok; } Napi::Value ThrowError(Napi::Env env, const char* message) { Napi::Error::New(env, message).ThrowAsJavaScriptException(); return env.Undefined(); } Napi::Value ThrowTypeError(Napi::Env env, const char* message) { Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); return env.Undefined(); } Napi::Value ThrowRangeError(Napi::Env env, const char* message) { Napi::RangeError::New(env, message).ThrowAsJavaScriptException(); return env.Undefined(); } // Unwraps a native-backed object into its associated C++ instance. This must // only be used on values that are already known to be backed by the given // class (e.g., a receiver that was validated by TypeSafeCallback below, or a // value produced by our own code), because napi_unwrap does not check the // object's type. Returns NULL if the environment can no longer run JavaScript // (e.g., worker.terminate()), so callers should use UNWRAP_OR_RETURN, which // checks for that case. template inline T* Unwrap(Napi::Value value) { return T::Unwrap(value.As()); } // Generates a random type tag when the addon is loaded. Type tags must not be // hardcoded, because multiple copies (or even different versions) of the addon // can be loaded into a single process (see the "nativeBinding" option), and // objects created by one copy must not be accepted by another copy's methods, // whose class layouts may differ. inline napi_type_tag RandomTypeTag() { std::random_device rd; auto random64 = [&rd]() { return (static_cast(rd()) << 32) | static_cast(rd()); }; return { random64(), random64() }; } // Determines whether the given value is an object that is backed by the given // native class. Each native class is identified by a unique type tag, which // gets applied to each of its instances upon construction. template inline bool IsInstanceOf(Napi::Env env, Napi::Value value) { bool result = false; napi_status status = napi_check_object_type_tag(env, value.As(), &T::TYPE_TAG, &result); return status == napi_ok && result; } // Wraps a native method so that it can only be invoked on a receiver that is // backed by the expected native class. Node-API has no equivalent of // v8::Signature, and napi_unwrap alone does not check the object's type, so // without this check, a method borrowed onto a foreign wrapped object (e.g., // db.exec.call(stmt)) would reinterpret the wrapped pointer as the wrong type. template napi_value TypeSafeCallback(napi_env env, napi_callback_info info) { Napi::CallbackInfo cbinfo(env, info); if (!IsInstanceOf(env, cbinfo.This())) { Napi::TypeError::New(env, "Illegal invocation").ThrowAsJavaScriptException(); return NULL; } return method(cbinfo); } // These match the default attributes of properties created by a V8 template. constexpr napi_property_attributes DEFAULT_ATTRIBUTES = static_cast(napi_writable | napi_enumerable | napi_configurable); template napi_property_descriptor PrototypeMethod(const char* name, Addon* addon) { napi_property_descriptor desc = {}; desc.utf8name = name; desc.method = TypeSafeCallback; desc.attributes = DEFAULT_ATTRIBUTES; desc.data = addon; return desc; } template napi_property_descriptor PrototypeSymbolMethod(Napi::Symbol symbol, Addon* addon) { napi_property_descriptor desc = {}; desc.name = symbol; desc.method = TypeSafeCallback; desc.attributes = DEFAULT_ATTRIBUTES; desc.data = addon; return desc; } // Defines a getter as an own property of the given object. V8 exposed these // getters as native data properties on each instance, which made them visible // to console.log(); Node-API has no native data properties, so an own accessor // property is the closest equivalent (unlike a prototype accessor, it is still // displayed by console.log(), albeit as [Getter]). template void SetInstanceGetter(Napi::Object obj, const char* name, Addon* addon) { napi_property_descriptor desc = {}; desc.utf8name = name; desc.getter = TypeSafeCallback; desc.attributes = napi_enumerable; desc.data = addon; napi_status status = napi_define_properties(obj.Env(), obj, 1, &desc); assert(status == napi_ok || status == napi_cannot_run_js); ((void)status); } // Determines whether to skip the given character at the start of an SQL string. inline bool IS_SKIPPED(char c) { return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); } // Allocates an empty array, without calling constructors/initializers. template inline T* ALLOC_ARRAY(size_t count) { return static_cast(::operator new[](count * sizeof(T))); } // Deallocates an array, without calling destructors. template inline void FREE_ARRAY(T* array_pointer) { ::operator delete[](array_pointer); }