diff --git a/.github/azure-pipelines.yml b/.github/azure-pipelines.yml index 32d6bd0b..c283c6ff 100644 --- a/.github/azure-pipelines.yml +++ b/.github/azure-pipelines.yml @@ -42,6 +42,12 @@ jobs: platform: x64 jsEngine: V8 + - template: jobs/win32.yml + parameters: + name: Win32_x64_QuickJS + platform: x64 + jsEngine: QuickJS + # UWP - template: jobs/uwp.yml parameters: @@ -78,6 +84,11 @@ jobs: name: Android_V8 jsEngine: V8 + - template: jobs/android.yml + parameters: + name: Android_QuickJS + jsEngine: QuickJS + # macOS - template: jobs/macos.yml parameters: @@ -114,13 +125,20 @@ jobs: - template: jobs/linux.yml parameters: - name: Ubuntu_clang + name: Ubuntu_clang_javascriptcore CC: clang CXX: clang++ - template: jobs/linux.yml parameters: - name: Ubuntu_Sanitizers_clang + name: Ubuntu_Sanitizers_clang_javascriptcore enableSanitizers: true CC: clang CXX: clang++ + + - template: jobs/linux.yml + parameters: + name: Ubuntu_clang_quickjs + jsEngine: QuickJS + CC: clang + CXX: clang++ diff --git a/.github/jobs/linux.yml b/.github/jobs/linux.yml index 93a068c9..3248a562 100644 --- a/.github/jobs/linux.yml +++ b/.github/jobs/linux.yml @@ -3,6 +3,7 @@ parameters: enableSanitizers: false CC: gcc CXX: g++ + jsEngine: 'JavaScriptCore' jobs: - job: ${{parameters.name}} @@ -23,7 +24,7 @@ jobs: - script: | export CC=${{parameters.CC}} export CXX=${{parameters.CXX}} - cmake -B Build/ubuntu -G Ninja -D CMAKE_BUILD_TYPE=RelWithDebInfo -D ENABLE_SANITIZERS=$(SANITIZER_FLAG) -D CMAKE_C_COMPILER=${{parameters.CC}} -D CMAKE_CXX_COMPILER=${{parameters.CXX}} + cmake -B Build/ubuntu -G Ninja -D CMAKE_BUILD_TYPE=Release -D ENABLE_SANITIZERS=$(SANITIZER_FLAG) -D CMAKE_C_COMPILER=${{parameters.CC}} -D CMAKE_CXX_COMPILER=${{parameters.CXX}} -D NAPI_JAVASCRIPT_ENGINE=${{parameters.jsEngine}} displayName: 'Configure CMake' - script: | diff --git a/.github/jobs/macos.yml b/.github/jobs/macos.yml index 01e029ee..8af15df9 100644 --- a/.github/jobs/macos.yml +++ b/.github/jobs/macos.yml @@ -29,9 +29,9 @@ jobs: scheme: 'UnitTests' sdk: 'macosx' useXcpretty: false - configuration: RelWithDebInfo + configuration: Release displayName: 'Build Xcode Project' - script: ./UnitTests - workingDirectory: 'Build/macOS/Tests/UnitTests/RelWithDebInfo' + workingDirectory: 'Build/macOS/Tests/UnitTests/Release' displayName: 'Run Tests' diff --git a/.github/jobs/win32.yml b/.github/jobs/win32.yml index 2ea7a402..4accaa92 100644 --- a/.github/jobs/win32.yml +++ b/.github/jobs/win32.yml @@ -21,7 +21,7 @@ jobs: inputs: solution: 'Build/Win32/JsRuntimeHost.sln' maximumCpuCount: true - configuration: 'RelWithDebInfo' + configuration: 'Release' displayName: 'Build Solution' - script: | @@ -32,12 +32,12 @@ jobs: displayName: 'Enable Crash Dumps' - script: UnitTests.exe - workingDirectory: 'Build/Win32/Tests/UnitTests/RelWithDebInfo' + workingDirectory: 'Build/Win32/Tests/UnitTests/Release' displayName: 'Run Tests' - task: CopyFiles@2 inputs: - sourceFolder: 'Build/Win32/Tests/UnitTests/RelWithDebInfo' + sourceFolder: 'Build/Win32/Tests/UnitTests/Release' contents: UnitTests.* targetFolder: '$(Build.ArtifactStagingDirectory)/Dumps' cleanTargetFolder: false diff --git a/CMakeLists.txt b/CMakeLists.txt index dc02c3d3..a9c45ba9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,11 @@ FetchContent_Declare(UrlLib GIT_REPOSITORY https://github.com/BabylonJS/UrlLib.git GIT_TAG 1f9d2c05f792d994b09fdfc1129b25294efbe182 EXCLUDE_FROM_ALL) +FetchContent_Declare(quickjs-ng + GIT_REPOSITORY https://github.com/quickjs-ng/quickjs.git + GIT_TAG 97d23e51dcc835e42c4cd419fb4adee13baf0074 + EXCLUDE_FROM_ALL) + # -------------------------------------------------- FetchContent_MakeAvailable(CMakeExtensions) @@ -121,6 +126,11 @@ if(BABYLON_DEBUG_TRACE) add_definitions(-DBABYLON_DEBUG_TRACE) endif() +if(NAPI_JAVASCRIPT_ENGINE STREQUAL "QuickJS") + option(QJS_BUILD_LIBC "Build QuickJS with libc support." ON) + FetchContent_MakeAvailable_With_Message(quickjs-ng) +endif() + if(NAPI_JAVASCRIPT_ENGINE STREQUAL "V8" AND JSRUNTIMEHOST_CORE_APPRUNTIME_V8_INSPECTOR) FetchContent_MakeAvailable_With_Message(asio) add_library(asio INTERFACE) diff --git a/Core/AppRuntime/CMakeLists.txt b/Core/AppRuntime/CMakeLists.txt index ad8f670d..3eb275d8 100644 --- a/Core/AppRuntime/CMakeLists.txt +++ b/Core/AppRuntime/CMakeLists.txt @@ -37,6 +37,9 @@ if(NAPI_JAVASCRIPT_ENGINE STREQUAL "V8" AND JSRUNTIMEHOST_CORE_APPRUNTIME_V8_INS PRIVATE v8inspector) set_property(TARGET v8inspector PROPERTY FOLDER Dependencies) +elseif(NAPI_JAVASCRIPT_ENGINE STREQUAL "QuickJS") + target_link_libraries(AppRuntime PRIVATE qjs) + target_compile_definitions(AppRuntime PRIVATE USE_QUICKJS) endif() set_property(TARGET AppRuntime PROPERTY FOLDER Core) diff --git a/Core/AppRuntime/Source/AppRuntime_QuickJS.cpp b/Core/AppRuntime/Source/AppRuntime_QuickJS.cpp new file mode 100644 index 00000000..0adc125e --- /dev/null +++ b/Core/AppRuntime/Source/AppRuntime_QuickJS.cpp @@ -0,0 +1,46 @@ +#include "AppRuntime.h" +#include + +#ifdef _WIN32 +#pragma warning(push) +// cast from int64 to int32 +#pragma warning(disable : 4244) +#endif +#include +#ifdef _WIN32 +#pragma warning(pop) +#endif + +namespace Babylon +{ + void AppRuntime::RunEnvironmentTier(const char* /*executablePath*/) + { + // Create the runtime. + JSRuntime* runtime = JS_NewRuntime(); + if (!runtime) + { + throw std::runtime_error{"Failed to create QuickJS runtime"}; + } + + // Create the context. + JSContext* context = JS_NewContext(runtime); + if (!context) + { + JS_FreeRuntime(runtime); + throw std::runtime_error{"Failed to create QuickJS context"}; + } + + // Use the context within a scope. + { + Napi::Env env = Napi::Attach(context); + + Run(env); + + Napi::Detach(env); + } + + // Destroy the context and runtime. + JS_FreeContext(context); + JS_FreeRuntime(runtime); + } +} diff --git a/Core/AppRuntime/Source/WorkQueue.cpp b/Core/AppRuntime/Source/WorkQueue.cpp index 4c1c5f21..ab7e21f7 100644 --- a/Core/AppRuntime/Source/WorkQueue.cpp +++ b/Core/AppRuntime/Source/WorkQueue.cpp @@ -1,5 +1,17 @@ #include "WorkQueue.h" +#ifdef USE_QUICKJS +#ifdef _WIN32 +#pragma warning(push) +// cast from int64 to int32 +#pragma warning(disable : 4244) +#endif +#include +#ifdef _WIN32 +#pragma warning(pop) +#endif +#endif + namespace Babylon { WorkQueue::WorkQueue(std::function threadProcedure) @@ -42,6 +54,15 @@ namespace Babylon while (!m_cancelSource.cancelled()) { m_dispatcher.blocking_tick(m_cancelSource); +#ifdef USE_QUICKJS + // Process QuickJS microtasks (promise callbacks, etc.) + JSContext* pending_ctx; + int result; + while ((result = JS_ExecutePendingJob(JS_GetRuntime(Napi::GetContext(env)), &pending_ctx)) > 0) + { + // Keep draining the microtask queue + } +#endif } m_dispatcher.clear(); diff --git a/Core/Node-API/CMakeLists.txt b/Core/Node-API/CMakeLists.txt index 1e8b8611..1df029e1 100644 --- a/Core/Node-API/CMakeLists.txt +++ b/Core/Node-API/CMakeLists.txt @@ -39,7 +39,13 @@ if(NAPI_BUILD_ABI) endfunction() endif() - if(NAPI_JAVASCRIPT_ENGINE STREQUAL "Chakra") + if(NAPI_JAVASCRIPT_ENGINE STREQUAL "QuickJS") + set(SOURCES ${SOURCES} + "Source/env_quickjs.cc" + "Source/js_native_api_quickjs.cc" + "Source/js_native_api_quickjs.h") + set(LINK_LIBRARIES ${LINK_LIBRARIES} PRIVATE qjs) + elseif(NAPI_JAVASCRIPT_ENGINE STREQUAL "Chakra") set(SOURCES ${SOURCES} "Source/env_chakra.cc" "Source/js_native_api_chakra.cc" diff --git a/Core/Node-API/Include/Engine/QuickJS/napi/env.h b/Core/Node-API/Include/Engine/QuickJS/napi/env.h new file mode 100644 index 00000000..ad6081d4 --- /dev/null +++ b/Core/Node-API/Include/Engine/QuickJS/napi/env.h @@ -0,0 +1,15 @@ +#pragma once + +#include +struct JSContext; + +namespace Napi +{ + Napi::Env Attach(JSContext* context); + + void Detach(Napi::Env); + + Napi::Value Eval(Napi::Env env, const char* source, const char* sourceUrl); + + JSContext* GetContext(Napi::Env); +} diff --git a/Core/Node-API/Source/env_quickjs.cc b/Core/Node-API/Source/env_quickjs.cc new file mode 100644 index 00000000..141df414 --- /dev/null +++ b/Core/Node-API/Source/env_quickjs.cc @@ -0,0 +1,65 @@ +#include +#include "js_native_api_quickjs.h" +#include + +namespace Napi +{ + Env Attach(JSContext* context) + { + napi_env env_ptr{new napi_env__}; + env_ptr->context = context; + env_ptr->current_context = env_ptr->context; + + // Get Object.prototype.hasOwnProperty + JSValue global = JS_GetGlobalObject(context); + JSValue object = JS_GetPropertyStr(context, global, "Object"); + if (!JS_IsException(object) && JS_IsObject(object)) + { + JSValue prototype = JS_GetPrototype(context, object); + if (!JS_IsException(prototype) && JS_IsObject(prototype)) + { + JSValue hasOwnProperty = JS_GetPropertyStr(context, prototype, "hasOwnProperty"); + if (!JS_IsException(hasOwnProperty)) + { + env_ptr->has_own_property_function = hasOwnProperty; + } + else + { + JS_FreeValue(context, hasOwnProperty); + } + JS_FreeValue(context, prototype); + } + else if (JS_IsException(prototype)) + { + JS_FreeValue(context, prototype); + } + JS_FreeValue(context, object); + } + else if (JS_IsException(object)) + { + JS_FreeValue(context, object); + } + JS_FreeValue(context, global); + + return {env_ptr}; + } + + void Detach(Env env) + { + napi_env env_ptr{env}; + if (env_ptr) + { + if (!JS_IsUndefined(env_ptr->has_own_property_function)) + { + JS_FreeValue(env_ptr->context, env_ptr->has_own_property_function); + } + delete env_ptr; + } + } + + JSContext* GetContext(Env env) + { + napi_env env_ptr{env}; + return env_ptr->context; + } +} diff --git a/Core/Node-API/Source/js_native_api_quickjs.cc b/Core/Node-API/Source/js_native_api_quickjs.cc new file mode 100644 index 00000000..ede68a9f --- /dev/null +++ b/Core/Node-API/Source/js_native_api_quickjs.cc @@ -0,0 +1,2655 @@ +#include "js_native_api_quickjs.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +// Custom class ID for external objects +static JSClassID js_external_class_id = 0; +static JSClassID js_wrap_class_id = 0; + +// Adapter for external data + finalize callback +class ExternalData { + public: + ExternalData(napi_env env, void* data, napi_finalize finalize_cb, void* hint) + : _env(env), _data(data), _cb(finalize_cb), _hint(hint) {} + + void* Data() { return _data; } + + static void Finalize(JSRuntime *rt, JSValue val) { + void* opaque = JS_GetOpaque(val, js_external_class_id); + ExternalData* externalData = reinterpret_cast(opaque); + if (externalData != nullptr) { + if (externalData->_cb != nullptr) { + externalData->_cb(externalData->_env, externalData->_data, externalData->_hint); + } + delete externalData; + } + } + + private: + napi_env _env; + void* _data; + napi_finalize _cb; + void* _hint; +}; + +// CallbackInfo struct +struct CallbackInfo { + napi_value newTarget; + napi_value thisArg; + napi_value* argv; + void* data; + uint16_t argc; + bool isConstructCall; +}; + +// Adapter for external callback + callback data +class ExternalCallback { + public: + ExternalCallback(napi_env env, napi_callback cb, void* data) + : _env(env), _cb(cb), _data(data), newTarget(JS_UNDEFINED) {} + +static JSValue Callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) { + ExternalCallback* externalCallback = reinterpret_cast(JS_GetOpaque(func_data[0], js_external_class_id)); + if (!externalCallback || !externalCallback->_cb) { + return JS_UNDEFINED; + } + + // SAVE AND SET current execution context + JSContext* savedCtx = externalCallback->_env->current_context; + externalCallback->_env->current_context = ctx; + + napi_clear_last_error(externalCallback->_env); + + // Build argv array + std::vector args; + args.reserve(argc); + for (int i = 0; i < argc; i++) { + args.push_back(reinterpret_cast(const_cast(&argv[i]))); + } + + JSValue actualThis = JS_UNDEFINED; + bool isConstructCall = (magic == 1); + + // Handle constructor call + if (isConstructCall) { + JSValue prototypeProperty = JS_GetPropertyStr(ctx, externalCallback->newTarget, "prototype"); + + if (JS_IsException(prototypeProperty)) { + externalCallback->_env->current_context = savedCtx; // RESTORE + return JS_EXCEPTION; + } + + actualThis = JS_NewObjectProto(ctx, prototypeProperty); + JS_FreeValue(ctx, prototypeProperty); + + if (JS_IsException(actualThis)) { + externalCallback->_env->current_context = savedCtx; // RESTORE + return JS_EXCEPTION; + } + } else { + actualThis = JS_DupValue(ctx, this_val); + } + + CallbackInfo cbInfo; + cbInfo.thisArg = reinterpret_cast(&actualThis); + cbInfo.newTarget = reinterpret_cast(&externalCallback->newTarget); + cbInfo.isConstructCall = isConstructCall; + cbInfo.argc = argc; + cbInfo.argv = args.empty() ? nullptr : args.data(); + cbInfo.data = externalCallback->_data; + + napi_value callbackResult = nullptr; + + // Wrap callback invocation in try-catch to handle C++ exceptions + try { + callbackResult = externalCallback->_cb(externalCallback->_env, reinterpret_cast(&cbInfo)); + } catch (const std::exception& e) { + // C++ exception was thrown - convert to JavaScript exception + // The exception should already be set via ThrowAsJavaScriptException() + // but if not, create a generic error + if (!JS_HasException(ctx)) { + JS_ThrowInternalError(ctx, "Uncaught C++ exception: %s", e.what()); + } + JS_FreeValue(ctx, actualThis); + externalCallback->_env->current_context = savedCtx; + return JS_EXCEPTION; + } + + // Check for exception in the CURRENT execution context + if (JS_HasException(ctx)) { + JS_FreeValue(ctx, actualThis); + externalCallback->_env->current_context = savedCtx; // RESTORE + return JS_EXCEPTION; + } + + // Now handle the normal return value + JSValue returnValue; + + if (callbackResult == nullptr) { + returnValue = isConstructCall ? actualThis : JS_UNDEFINED; + } else { + JSValue cbResultValue = *reinterpret_cast(callbackResult); + + if (isConstructCall) { + if (JS_IsObject(cbResultValue)) { + returnValue = JS_DupValue(ctx, cbResultValue); + JS_FreeValue(ctx, actualThis); + } else { + returnValue = actualThis; + } + } else { + returnValue = JS_DupValue(ctx, cbResultValue); + JS_FreeValue(ctx, actualThis); + } + } + + externalCallback->_env->current_context = savedCtx; // RESTORE + return returnValue; +} + + static void Finalize(JSRuntime *rt, JSValue val) { + void* opaque = JS_GetOpaque(val, js_external_class_id); + ExternalCallback* externalCallback = reinterpret_cast(opaque); + delete externalCallback; + } + + JSValue newTarget; + + private: + napi_env _env; + napi_callback _cb; + void* _data; +}; + +// Reference info for preventing GC +struct RefInfo { + JSValue value; + uint32_t count; +}; + +// Initialize class IDs +void InitClassIds(JSRuntime* rt) { + static bool initialized = false; + if (!initialized) { + JS_NewClassID(rt, &js_external_class_id); + JS_NewClassID(rt, &js_wrap_class_id); + + JSClassDef external_class_def = { + "NapiExternal", + ExternalData::Finalize, + nullptr, + nullptr, + nullptr + }; + JS_NewClass(rt, js_external_class_id, &external_class_def); + + JSClassDef wrap_class_def = { + "NapiWrap", + ExternalData::Finalize, + nullptr, + nullptr, + nullptr + }; + JS_NewClass(rt, js_wrap_class_id, &wrap_class_def); + + initialized = true; + } +} + +// Helper to convert napi_value to JSValue +inline JSValue ToJSValue(napi_value val) { + if (val == nullptr) { + return JS_UNDEFINED; + } + return *reinterpret_cast(val); +} + +// Helper to create napi_value from JSValue +// Note: This returns a pointer to a temporary. The caller must handle lifetime. +inline napi_value FromJSValue(napi_env env, JSValue val) { + + JSContext* ctx = env->current_context; + // Store in current handle scope + auto ptr = std::make_unique(val); + napi_value result = reinterpret_cast(ptr.get()); + + /*napi_value result; + + JSValue* ptr = new JSValue(val); + result = reinterpret_cast(ptr);*/ + + if (env /*&& !env->handle_scope_stack.empty()*/) { + env->handle_scope_stack.push_back(std::move(ptr)); + } + + return result; +} + +// Helper for property attributes +int ToQuickJSPropertyFlags(napi_property_attributes attributes) { + int flags = 0; + if (attributes & napi_writable) + flags |= JS_PROP_WRITABLE; + if (attributes & napi_enumerable) + flags |= JS_PROP_ENUMERABLE; + if (attributes & napi_configurable) + flags |= JS_PROP_CONFIGURABLE; + return flags; +} + +// Error message table +static const char* error_messages[] = { + nullptr, + "Invalid argument", + "An object was expected", + "A string was expected", + "A string or symbol was expected", + "A function was expected", + "A number was expected", + "A boolean was expected", + "An array was expected", + "Unknown failure", + "An exception is pending", + "The async work item was cancelled", + "napi_escape_handle already called on scope", + "Invalid handle scope usage", + "Invalid callback scope usage", + "Thread-safe function queue is full", + "Thread-safe function handle is closing", + "A bigint was expected", + "A date was expected", + "An arraybuffer was expected", + "A detachable arraybuffer was expected", + "Would deadlock", + "No external buffers allowed", + "Cannot run JS", +}; + +} // anonymous namespace + +// Get last error message +napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info** result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + static_assert(std::size(error_messages) == napi_cannot_run_js + 1, + "Count of error messages must match count of error values"); + + if (env->last_error.error_code < std::size(error_messages)) { + env->last_error.error_message = error_messages[env->last_error.error_code]; + } + + *result = &env->last_error; + return napi_ok; +} + +// Get undefined +napi_status napi_get_undefined(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + static JSValue undefined = JS_UNDEFINED; + *result = reinterpret_cast(&undefined); + napi_clear_last_error(env); + return napi_ok; +} + +// Get null +napi_status napi_get_null(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + static JSValue null_val = JS_NULL; + *result = reinterpret_cast(&null_val); + napi_clear_last_error(env); + return napi_ok; +} + +// Get global +napi_status napi_get_global(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + JSValue global = JS_GetGlobalObject(env->context); + *result = FromJSValue(env, global); + napi_clear_last_error(env); + return napi_ok; +} + +// Get boolean +napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + static JSValue js_true = JS_TRUE; + static JSValue js_false = JS_FALSE; + *result = value ? reinterpret_cast(&js_true) : reinterpret_cast(&js_false); + napi_clear_last_error(env); + return napi_ok; +} + +// Create number (double) +napi_status napi_create_double(napi_env env, double value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = FromJSValue(env, JS_NewFloat64(env->context, value)); + napi_clear_last_error(env); + return napi_ok; +} + +// Create number (int32) +napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = FromJSValue(env, JS_NewInt32(env->context, value)); + napi_clear_last_error(env); + return napi_ok; +} + +// Create number (uint32) +napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = FromJSValue(env, JS_NewUint32(env->context, value)); + napi_clear_last_error(env); + return napi_ok; +} + +// Create number (int64) +napi_status napi_create_int64(napi_env env, int64_t value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = FromJSValue(env, JS_NewInt64(env->context, value)); + napi_clear_last_error(env); + return napi_ok; +} + +// Create string UTF8 +napi_status napi_create_string_utf8(napi_env env, const char* str, size_t length, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValue jsStr; + if (length == NAPI_AUTO_LENGTH) { + jsStr = JS_NewString(env->context, str); + } else { + jsStr = JS_NewStringLen(env->context, str, length); + } + + if (JS_IsException(jsStr)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsStr); + napi_clear_last_error(env); + return napi_ok; +} + +// Create string latin1 +napi_status napi_create_string_latin1(napi_env env, const char* str, size_t length, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + // QuickJS doesn't have native Latin-1 support, so we convert to UTF-8 + // For now, just treat as UTF-8 (this may not be fully correct for high bytes) + if (length == NAPI_AUTO_LENGTH) { + length = strlen(str); + } + + JSValue jsStr = JS_NewStringLen(env->context, str, length); + if (JS_IsException(jsStr)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsStr); + napi_clear_last_error(env); + return napi_ok; +} + +// Create string UTF16 +napi_status napi_create_string_utf16(napi_env env, const char16_t* str, size_t length, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + // Convert UTF-16 to UTF-8 for QuickJS + if (length == NAPI_AUTO_LENGTH) { + length = 0; + while (str[length] != 0) length++; + } + + // Simple UTF-16 to UTF-8 conversion + std::string utf8; + utf8.reserve(length * 3); // worst case + + for (size_t i = 0; i < length; i++) { + uint32_t cp = str[i]; + + // Handle surrogate pairs + if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < length) { + uint16_t low = str[i + 1]; + if (low >= 0xDC00 && low <= 0xDFFF) { + cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); + i++; + } + } + + if (cp < 0x80) { + utf8.push_back(static_cast(cp)); + } else if (cp < 0x800) { + utf8.push_back(static_cast(0xC0 | (cp >> 6))); + utf8.push_back(static_cast(0x80 | (cp & 0x3F))); + } else if (cp < 0x10000) { + utf8.push_back(static_cast(0xE0 | (cp >> 12))); + utf8.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (cp & 0x3F))); + } else { + utf8.push_back(static_cast(0xF0 | (cp >> 18))); + utf8.push_back(static_cast(0x80 | ((cp >> 12) & 0x3F))); + utf8.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (cp & 0x3F))); + } + } + + JSValue jsStr = JS_NewStringLen(env->context, utf8.c_str(), utf8.length()); + if (JS_IsException(jsStr)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsStr); + napi_clear_last_error(env); + return napi_ok; +} + +// Get value type +napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + + if (JS_IsNumber(jsValue)) { + *result = napi_number; + } else if (JS_IsBool(jsValue)) { + *result = napi_boolean; + } else if (JS_IsString(jsValue)) { + *result = napi_string; + } else if (JS_IsSymbol(jsValue)) { + *result = napi_symbol; + } else if (JS_IsObject(jsValue)) { + if (JS_IsFunction(env->context, jsValue)) { + *result = napi_function; + } else { + // Check if it's an external object + JSClassID class_id = JS_GetClassID(jsValue); + if (class_id == js_external_class_id) { + *result = napi_external; + } else { + *result = napi_object; + } + } + } else if (JS_IsNull(jsValue)) { + *result = napi_null; + } else if (JS_IsUndefined(jsValue)) { + *result = napi_undefined; + } else if (JS_IsBigInt(jsValue)) { + *result = napi_bigint; + } else { + *result = napi_undefined; + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Get value double +napi_status napi_get_value_double(napi_env env, napi_value value, double* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + if (JS_ToFloat64(env->context, result, jsValue) < 0) { + return napi_set_last_error(env, napi_number_expected); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Get value int32 +napi_status napi_get_value_int32(napi_env env, napi_value value, int32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + if (JS_ToInt32(env->context, result, jsValue) < 0) { + return napi_set_last_error(env, napi_number_expected); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Get value uint32 +napi_status napi_get_value_uint32(napi_env env, napi_value value, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + if (JS_ToUint32(env->context, result, jsValue) < 0) { + return napi_set_last_error(env, napi_number_expected); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Get value int64 +napi_status napi_get_value_int64(napi_env env, napi_value value, int64_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + + double d; + if (JS_ToFloat64(env->context, &d, jsValue) < 0) { + return napi_set_last_error(env, napi_number_expected); + } + + if (std::isfinite(d)) { + *result = static_cast(d); + } else { + *result = 0; + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Get value bool +napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + + if (!JS_IsBool(jsValue)) { + return napi_set_last_error(env, napi_boolean_expected); + } + + *result = JS_VALUE_GET_BOOL(jsValue) != 0; + napi_clear_last_error(env); + return napi_ok; +} + +// Get value string UTF8 +napi_status napi_get_value_string_utf8(napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + JSValue jsValue = ToJSValue(value); + + if (!JS_IsString(jsValue)) { + return napi_set_last_error(env, napi_string_expected); + } + + size_t len; + const char* str = JS_ToCStringLen(env->context, &len, jsValue); + if (!str) { + return napi_set_last_error(env, napi_string_expected); + } + + if (buf == nullptr) { + if (result != nullptr) { + *result = len; + } + } else if (bufsize > 0) { + size_t copy_len = std::min(len, bufsize - 1); + memcpy(buf, str, copy_len); + buf[copy_len] = '\0'; + if (result != nullptr) { + *result = copy_len; + } + } + + JS_FreeCString(env->context, str); + napi_clear_last_error(env); + return napi_ok; +} + +// Get value string latin1 +napi_status napi_get_value_string_latin1(napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result) { + // For simplicity, treat same as UTF-8 + return napi_get_value_string_utf8(env, value, buf, bufsize, result); +} + +// Get value string UTF16 +napi_status napi_get_value_string_utf16(napi_env env, napi_value value, char16_t* buf, size_t bufsize, size_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + + JSValue jsValue = ToJSValue(value); + + if (!JS_IsString(jsValue)) { + return napi_set_last_error(env, napi_string_expected); + } + + size_t len; + const char* str = JS_ToCStringLen(env->context, &len, jsValue); + if (!str) { + return napi_set_last_error(env, napi_string_expected); + } + + // Convert UTF-8 to UTF-16 + std::vector utf16; + utf16.reserve(len); + + for (size_t i = 0; i < len; ) { + uint32_t cp; + unsigned char c = str[i]; + + if ((c & 0x80) == 0) { + cp = c; + i++; + } else if ((c & 0xE0) == 0xC0) { + cp = (c & 0x1F) << 6; + if (i + 1 < len) cp |= (str[i+1] & 0x3F); + i += 2; + } else if ((c & 0xF0) == 0xE0) { + cp = (c & 0x0F) << 12; + if (i + 1 < len) cp |= (str[i+1] & 0x3F) << 6; + if (i + 2 < len) cp |= (str[i+2] & 0x3F); + i += 3; + } else if ((c & 0xF8) == 0xF0) { + cp = (c & 0x07) << 18; + if (i + 1 < len) cp |= (str[i+1] & 0x3F) << 12; + if (i + 2 < len) cp |= (str[i+2] & 0x3F) << 6; + if (i + 3 < len) cp |= (str[i+3] & 0x3F); + i += 4; + } else { + cp = 0xFFFD; // replacement character + i++; + } + + if (cp < 0x10000) { + utf16.push_back(static_cast(cp)); + } else { + cp -= 0x10000; + utf16.push_back(static_cast(0xD800 + (cp >> 10))); + utf16.push_back(static_cast(0xDC00 + (cp & 0x3FF))); + } + } + + JS_FreeCString(env->context, str); + + if (buf == nullptr) { + if (result != nullptr) { + *result = utf16.size(); + } + } else if (bufsize > 0) { + size_t copy_len = std::min(utf16.size(), bufsize - 1); + memcpy(buf, utf16.data(), copy_len * sizeof(char16_t)); + buf[copy_len] = 0; + if (result != nullptr) { + *result = copy_len; + } + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Coerce to bool +napi_status napi_coerce_to_bool(napi_env env, napi_value value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + int boolResult = JS_ToBool(env->context, jsValue); + if (boolResult < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + static JSValue js_true = JS_TRUE; + static JSValue js_false = JS_FALSE; + *result = boolResult ? reinterpret_cast(&js_true) : reinterpret_cast(&js_false); + napi_clear_last_error(env); + return napi_ok; +} + +// Coerce to number +napi_status napi_coerce_to_number(napi_env env, napi_value value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + double num; + if (JS_ToFloat64(env->context, &num, jsValue) < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, JS_NewFloat64(env->context, num)); + napi_clear_last_error(env); + return napi_ok; +} + +// Coerce to string +napi_status napi_coerce_to_string(napi_env env, napi_value value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + JSValue jsString = JS_ToString(env->context, jsValue); + + if (JS_IsException(jsString)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsString); + napi_clear_last_error(env); + return napi_ok; +} + +// Coerce to object +napi_status napi_coerce_to_object(napi_env env, napi_value value, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + + JSValue global = JS_GetGlobalObject(env->context); + JSValue objectCtor = JS_GetPropertyStr(env->context, global, "Object"); + JSValueConst args[1] = { jsValue }; + JSValue jsObject = JS_Call(env->context, objectCtor, JS_UNDEFINED, 1, args); + + JS_FreeValue(env->context, objectCtor); + JS_FreeValue(env->context, global); + + if (JS_IsException(jsObject)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsObject); + napi_clear_last_error(env); + return napi_ok; +} + +// Get prototype +napi_status napi_get_prototype(napi_env env, napi_value object, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSValue prototype = JS_GetPrototype(env->context, jsObject); + + if (JS_IsException(prototype)) { + return napi_set_last_error(env, napi_object_expected); + } + + *result = FromJSValue(env, prototype); + napi_clear_last_error(env); + return napi_ok; +} + +// Create object +napi_status napi_create_object(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValue obj = JS_NewObject(env->context); + if (JS_IsException(obj)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, obj); + napi_clear_last_error(env); + return napi_ok; +} + +// Create array +napi_status napi_create_array(napi_env env, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValue arr = JS_NewArray(env->context); + if (JS_IsException(arr)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, arr); + napi_clear_last_error(env); + return napi_ok; +} + +// Create array with length +napi_status napi_create_array_with_length(napi_env env, size_t length, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValue arr = JS_NewArray(env->context); + if (JS_IsException(arr)) { + return napi_set_last_error(env, napi_generic_failure); + } + + JSAtom lengthAtom = JS_NewAtom(env->context, "length"); + JS_SetProperty(env->context, arr, lengthAtom, JS_NewUint32(env->context, static_cast(length))); + JS_FreeAtom(env->context, lengthAtom); + + *result = FromJSValue(env, arr); + napi_clear_last_error(env); + return napi_ok; +} + +// Get array length +napi_status napi_get_array_length(napi_env env, napi_value value, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + JSValue lenVal = JS_GetPropertyStr(env->context, jsValue, "length"); + + if (JS_IsException(lenVal)) { + return napi_set_last_error(env, napi_array_expected); + } + + if (JS_ToUint32(env->context, result, lenVal) < 0) { + JS_FreeValue(env->context, lenVal); + return napi_set_last_error(env, napi_number_expected); + } + + JS_FreeValue(env->context, lenVal); + napi_clear_last_error(env); + return napi_ok; +} + +// Internal helper to create function with custom magic value +static napi_status create_function_internal(napi_env env, const char* utf8name, size_t length, napi_callback cb, void* data, int magic, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, cb); + CHECK_ARG(env, result); + + JSRuntime* rt = JS_GetRuntime(env->context); + InitClassIds(rt); + + ExternalCallback* externalCallback = new ExternalCallback(env, cb, data); + + // Create opaque object to hold callback data + JSValue callbackData = JS_NewObjectClass(env->context, js_external_class_id); + JS_SetOpaque(callbackData, externalCallback); + + // Create function with data - magic parameter determines if it's a constructor call + JSValueConst funcDataArray[1] = { callbackData }; + JSValue func = JS_NewCFunctionData(env->context, ExternalCallback::Callback, 0, magic, 1, funcDataArray); + + if (JS_IsException(func)) { + delete externalCallback; + JS_FreeValue(env->context, callbackData); + return napi_set_last_error(env, napi_generic_failure); + } + + // Set function name if provided + if (utf8name != nullptr) { + size_t name_len = (length == NAPI_AUTO_LENGTH) ? strlen(utf8name) : length; + JSAtom nameAtom = JS_NewAtomLen(env->context, utf8name, name_len); + JS_DefinePropertyValue(env->context, func, JS_NewAtom(env->context, "name"), + JS_AtomToString(env->context, nameAtom), 0); + JS_FreeAtom(env->context, nameAtom); + } + + externalCallback->newTarget = JS_DupValue(env->context, func); + + *result = FromJSValue(env, func); + napi_clear_last_error(env); + return napi_ok; +} + +// Create function (regular function with magic=0) +napi_status napi_create_function(napi_env env, const char* utf8name, size_t length, napi_callback cb, void* data, napi_value* result) { + return create_function_internal(env, utf8name, length, cb, data, 0, result); +} + +// Create function for use as constructor (magic=1) +napi_status napi_create_function_with_magic(napi_env env, const char* utf8name, size_t length, napi_callback cb, void* data, napi_value* result) { + return create_function_internal(env, utf8name, length, cb, data, 1, result); +} + +// Get cb info +napi_status napi_get_cb_info(napi_env env, napi_callback_info cbinfo, size_t* argc, napi_value* argv, napi_value* this_arg, void** data) { + CHECK_ENV(env); + CHECK_ARG(env, cbinfo); + + CallbackInfo* info = reinterpret_cast(cbinfo); + + if (argv != nullptr && argc != nullptr) { + size_t i = 0; + size_t min = std::min(*argc, static_cast(info->argc)); + + for (; i < min; i++) { + argv[i] = info->argv[i]; + } + + // Fill remaining with undefined + static JSValue undefined = JS_UNDEFINED; + for (; i < *argc; i++) { + argv[i] = reinterpret_cast(&undefined); + } + } + + if (argc != nullptr) { + *argc = info->argc; + } + + if (this_arg != nullptr) { + *this_arg = info->thisArg; + } + + if (data != nullptr) { + *data = info->data; + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Get new target +napi_status napi_get_new_target(napi_env env, napi_callback_info cbinfo, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, cbinfo); + CHECK_ARG(env, result); + + CallbackInfo* info = reinterpret_cast(cbinfo); + + if (info->isConstructCall) { + *result = info->newTarget; + } else { + static JSValue undefined = JS_UNDEFINED; + *result = reinterpret_cast(&undefined); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Property operations +napi_status napi_get_property(napi_env env, napi_value object, napi_value key, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, key); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSValue jsKey = ToJSValue(key); + + JSAtom atom = JS_ValueToAtom(env->context, jsKey); + if (atom == JS_ATOM_NULL) { + return napi_set_last_error(env, napi_generic_failure); + } + + JSValue jsResult = JS_GetProperty(env->context, jsObject, atom); + JS_FreeAtom(env->context, atom); + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsResult); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_set_property(napi_env env, napi_value object, napi_value key, napi_value value) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, key); + CHECK_ARG(env, value); + + JSValue jsObject = ToJSValue(object); + JSValue jsKey = ToJSValue(key); + JSValue jsValue = ToJSValue(value); + + JSAtom atom = JS_ValueToAtom(env->context, jsKey); + if (atom == JS_ATOM_NULL) { + return napi_set_last_error(env, napi_generic_failure); + } + + int ret = JS_SetProperty(env->context, jsObject, atom, JS_DupValue(env->context, jsValue)); + JS_FreeAtom(env->context, atom); + + if (ret < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_has_property(napi_env env, napi_value object, napi_value key, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, key); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSValue jsKey = ToJSValue(key); + + JSAtom atom = JS_ValueToAtom(env->context, jsKey); + if (atom == JS_ATOM_NULL) { + return napi_set_last_error(env, napi_generic_failure); + } + + int has = JS_HasProperty(env->context, jsObject, atom); + JS_FreeAtom(env->context, atom); + + if (has < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = (has != 0); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_delete_property(napi_env env, napi_value object, napi_value key, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, key); + + JSValue jsObject = ToJSValue(object); + JSValue jsKey = ToJSValue(key); + + JSAtom atom = JS_ValueToAtom(env->context, jsKey); + if (atom == JS_ATOM_NULL) { + return napi_set_last_error(env, napi_generic_failure); + } + + int deleted = JS_DeleteProperty(env->context, jsObject, atom, 0); + JS_FreeAtom(env->context, atom); + + if (deleted < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + if (result != nullptr) { + *result = (deleted != 0); + } + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_has_own_property(napi_env env, napi_value object, napi_value key, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, key); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSValue jsKey = ToJSValue(key); + + // Use the cached hasOwnProperty function + if (!JS_IsUndefined(env->has_own_property_function)) { + JSValueConst args[1] = { jsKey }; + JSValue hasResult = JS_Call(env->context, env->has_own_property_function, jsObject, 1, args); + + if (JS_IsException(hasResult)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = JS_ToBool(env->context, hasResult) != 0; + JS_FreeValue(env->context, hasResult); + } else { + // Fallback: check if property exists and is own + JSAtom atom = JS_ValueToAtom(env->context, jsKey); + if (atom == JS_ATOM_NULL) { + return napi_set_last_error(env, napi_generic_failure); + } + + JSPropertyDescriptor desc; + int ret = JS_GetOwnProperty(env->context, &desc, jsObject, atom); + JS_FreeAtom(env->context, atom); + + if (ret < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = (ret == 1); + + if (ret == 1) { + JS_FreeValue(env->context, desc.value); + JS_FreeValue(env->context, desc.getter); + JS_FreeValue(env->context, desc.setter); + } + } + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_get_named_property(napi_env env, napi_value object, const char* utf8name, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, utf8name); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSValue jsResult = JS_GetPropertyStr(env->context, jsObject, utf8name); + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsResult); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8name, napi_value value) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, utf8name); + CHECK_ARG(env, value); + + JSValue jsObject = ToJSValue(object); + JSValue jsValue = ToJSValue(value); + + if (JS_SetPropertyStr(env->context, jsObject, utf8name, JS_DupValue(env->context, jsValue)) < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_has_named_property(napi_env env, napi_value object, const char* utf8name, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, utf8name); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSAtom atom = JS_NewAtom(env->context, utf8name); + + int has = JS_HasProperty(env->context, jsObject, atom); + JS_FreeAtom(env->context, atom); + + if (has < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = (has != 0); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSValue jsResult = JS_GetPropertyUint32(env->context, jsObject, index); + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, jsResult); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_set_element(napi_env env, napi_value object, uint32_t index, napi_value value) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, value); + + JSValue jsObject = ToJSValue(object); + JSValue jsValue = ToJSValue(value); + + if (JS_SetPropertyUint32(env->context, jsObject, index, JS_DupValue(env->context, jsValue)) < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_has_element(napi_env env, napi_value object, uint32_t index, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + JSAtom atom = JS_NewAtomUInt32(env->context, index); + + int has = JS_HasProperty(env->context, jsObject, atom); + JS_FreeAtom(env->context, atom); + + if (has < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = (has != 0); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_delete_element(napi_env env, napi_value object, uint32_t index, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + + JSValue jsObject = ToJSValue(object); + JSAtom atom = JS_NewAtomUInt32(env->context, index); + + int deleted = JS_DeleteProperty(env->context, jsObject, atom, 0); + JS_FreeAtom(env->context, atom); + + if (deleted < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + if (result != nullptr) { + *result = (deleted != 0); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Get property names +napi_status napi_get_property_names(napi_env env, napi_value object, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, object); + CHECK_ARG(env, result); + + JSValue jsObject = ToJSValue(object); + + JSPropertyEnum* ptab; + uint32_t plen; + + if (JS_GetOwnPropertyNames(env->context, &ptab, &plen, jsObject, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) { + return napi_set_last_error(env, napi_generic_failure); + } + + JSValue arr = JS_NewArray(env->context); + + for (uint32_t i = 0; i < plen; i++) { + JSValue name = JS_AtomToString(env->context, ptab[i].atom); + JS_SetPropertyUint32(env->context, arr, i, name); + } + + JS_FreePropertyEnum(env->context, ptab, plen); + + *result = FromJSValue(env, arr); + napi_clear_last_error(env); + return napi_ok; +} + +// Define properties +napi_status napi_define_properties(napi_env env, napi_value object, size_t property_count, const napi_property_descriptor* properties) { + CHECK_ENV(env); + if (property_count > 0) { + CHECK_ARG(env, properties); + } + + JSValue jsObject = ToJSValue(object); + + for (size_t i = 0; i < property_count; i++) { + const napi_property_descriptor* p = &properties[i]; + + JSAtom atom; + if (p->utf8name != nullptr) { + atom = JS_NewAtom(env->context, p->utf8name); + } else { + atom = JS_ValueToAtom(env->context, ToJSValue(p->name)); + } + + if (atom == JS_ATOM_NULL) { + return napi_set_last_error(env, napi_name_expected); + } + + int flags = ToQuickJSPropertyFlags(p->attributes); + + if (p->getter != nullptr || p->setter != nullptr) { + // Accessor property + JSValue getter = JS_UNDEFINED; + JSValue setter = JS_UNDEFINED; + + if (p->getter != nullptr) { + napi_value getterFunc; + napi_status status = napi_create_function(env, nullptr, 0, p->getter, p->data, &getterFunc); + if (status != napi_ok) { + JS_FreeAtom(env->context, atom); + return status; + } + getter = JS_DupValue(env->context, ToJSValue(getterFunc)); + } + + if (p->setter != nullptr) { + napi_value setterFunc; + napi_status status = napi_create_function(env, nullptr, 0, p->setter, p->data, &setterFunc); + if (status != napi_ok) { + JS_FreeValue(env->context, getter); + JS_FreeAtom(env->context, atom); + return status; + } + setter = JS_DupValue(env->context, ToJSValue(setterFunc)); + } + + flags |= JS_PROP_HAS_GET | JS_PROP_HAS_SET | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE; + JS_DefinePropertyGetSet(env->context, jsObject, atom, getter, setter, flags); + } else if (p->method != nullptr) { + // Method + napi_value methodFunc; + napi_status status = napi_create_function(env, p->utf8name, NAPI_AUTO_LENGTH, p->method, p->data, &methodFunc); + if (status != napi_ok) { + JS_FreeAtom(env->context, atom); + return status; + } + + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE; + JS_DefinePropertyValue(env->context, jsObject, atom, JS_DupValue(env->context, ToJSValue(methodFunc)), flags); + } else if (p->value != nullptr) { + // Data property + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE; + JS_DefinePropertyValue(env->context, jsObject, atom, JS_DupValue(env->context, ToJSValue(p->value)), flags); + } + + JS_FreeAtom(env->context, atom); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Call function +napi_status napi_call_function(napi_env env, napi_value recv, napi_value func, size_t argc, const napi_value* argv, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, recv); + CHECK_ARG(env, func); + if (argc > 0) { + CHECK_ARG(env, argv); + } + + JSValue jsRecv = ToJSValue(recv); + JSValue jsFunc = ToJSValue(func); + + std::vector args; + args.reserve(argc); + for (size_t i = 0; i < argc; i++) { + args.push_back(ToJSValue(argv[i])); + } + + JSValue jsResult = JS_Call(env->context, jsFunc, jsRecv, static_cast(argc), args.data()); + + if (JS_IsException(jsResult)) { + if (result != nullptr) { + static JSValue undefined = JS_UNDEFINED; + *result = reinterpret_cast(&undefined); + } + return napi_set_last_error(env, napi_pending_exception); + } + + if (result != nullptr) { + *result = FromJSValue(env, jsResult); + } else { + JS_FreeValue(env->context, jsResult); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// New instance +napi_status napi_new_instance(napi_env env, napi_value constructor, size_t argc, const napi_value* argv, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, constructor); + if (argc > 0) { + CHECK_ARG(env, argv); + } + CHECK_ARG(env, result); + + JSValue jsCtor = ToJSValue(constructor); + + std::vector args; + args.reserve(argc); + for (size_t i = 0; i < argc; i++) { + args.push_back(ToJSValue(argv[i])); + } + + JSValue jsResult = JS_CallConstructor(env->context, jsCtor, static_cast(argc), args.data()); + + if (JS_IsException(jsResult)) { + return napi_set_last_error(env, napi_pending_exception); + } + + *result = FromJSValue(env, jsResult); + napi_clear_last_error(env); + return napi_ok; +} + +// Is array +napi_status napi_is_array(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + *result = JS_IsArray(jsValue) != 0; + + napi_clear_last_error(env); + return napi_ok; +} + +// Throw +napi_status napi_throw(napi_env env, napi_value error) { + CHECK_ENV(env); + CHECK_ARG(env, error); + + JSValue jsError = ToJSValue(error); + + // Throw into the CURRENT execution context, not the stored context + JSContext* targetCtx = env->current_context ? env->current_context : env->context; + JS_Throw(targetCtx, JS_DupValue(targetCtx, jsError)); + + return napi_set_last_error(env, napi_pending_exception); +} + +// Throw error +napi_status napi_throw_error(napi_env env, const char* code, const char* msg) { + CHECK_ENV(env); + + JSValue error = JS_NewError(env->context); + if (msg) { + JS_SetPropertyStr(env->context, error, "message", JS_NewString(env->context, msg)); + } + if (code) { + JS_SetPropertyStr(env->context, error, "code", JS_NewString(env->context, code)); + } + + JS_Throw(env->context, error); + return napi_set_last_error(env, napi_pending_exception); +} + +// Throw type error +napi_status napi_throw_type_error(napi_env env, const char* code, const char* msg) { + CHECK_ENV(env); + + JS_ThrowTypeError(env->context, "%s", msg ? msg : ""); + return napi_set_last_error(env, napi_pending_exception); +} + +// Throw range error +napi_status napi_throw_range_error(napi_env env, const char* code, const char* msg) { + CHECK_ENV(env); + + JS_ThrowRangeError(env->context, "%s", msg ? msg : ""); + return napi_set_last_error(env, napi_pending_exception); +} + +// Create error +napi_status napi_create_error(napi_env env, napi_value code, napi_value msg, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + JSValue error = JS_NewError(env->context); + JS_SetPropertyStr(env->context, error, "message", JS_DupValue(env->context, ToJSValue(msg))); + + if (code != nullptr) { + JS_SetPropertyStr(env->context, error, "code", JS_DupValue(env->context, ToJSValue(code))); + } + + *result = FromJSValue(env, error); + napi_clear_last_error(env); + return napi_ok; +} + +// Create type error +napi_status napi_create_type_error(napi_env env, napi_value code, napi_value msg, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + JSValue global = JS_GetGlobalObject(env->context); + JSValue typeErrorCtor = JS_GetPropertyStr(env->context, global, "TypeError"); + + JSValueConst args[1] = { ToJSValue(msg) }; + JSValue error = JS_CallConstructor(env->context, typeErrorCtor, 1, args); + + if (code != nullptr) { + JS_SetPropertyStr(env->context, error, "code", JS_DupValue(env->context, ToJSValue(code))); + } + + JS_FreeValue(env->context, typeErrorCtor); + JS_FreeValue(env->context, global); + + *result = FromJSValue(env, error); + napi_clear_last_error(env); + return napi_ok; +} + +// Create range error +napi_status napi_create_range_error(napi_env env, napi_value code, napi_value msg, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, msg); + CHECK_ARG(env, result); + + JSValue global = JS_GetGlobalObject(env->context); + JSValue rangeErrorCtor = JS_GetPropertyStr(env->context, global, "RangeError"); + + JSValueConst args[1] = { ToJSValue(msg) }; + JSValue error = JS_CallConstructor(env->context, rangeErrorCtor, 1, args); + + if (code != nullptr) { + JS_SetPropertyStr(env->context, error, "code", JS_DupValue(env->context, ToJSValue(code))); + } + + JS_FreeValue(env->context, rangeErrorCtor); + JS_FreeValue(env->context, global); + + *result = FromJSValue(env, error); + napi_clear_last_error(env); + return napi_ok; +} + +// Is error +napi_status napi_is_error(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + *result = JS_IsError(jsValue) != 0; + + napi_clear_last_error(env); + return napi_ok; +} + +// Strict equals +napi_status napi_strict_equals(napi_env env, napi_value lhs, napi_value rhs, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, lhs); + CHECK_ARG(env, rhs); + CHECK_ARG(env, result); + + JSValue jsLhs = ToJSValue(lhs); + JSValue jsRhs = ToJSValue(rhs); + + // *result = JS_StrictEq(env->context, jsLhs, jsRhs) != 0; // TODO + + napi_clear_last_error(env); + return napi_ok; +} + +// Create symbol +napi_status napi_create_symbol(napi_env env, napi_value description, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValue global = JS_GetGlobalObject(env->context); + JSValue symbolCtor = JS_GetPropertyStr(env->context, global, "Symbol"); + + JSValue symbol; + if (description != nullptr) { + JSValueConst args[1] = { ToJSValue(description) }; + symbol = JS_Call(env->context, symbolCtor, JS_UNDEFINED, 1, args); + } else { + symbol = JS_Call(env->context, symbolCtor, JS_UNDEFINED, 0, nullptr); + } + + JS_FreeValue(env->context, symbolCtor); + JS_FreeValue(env->context, global); + + if (JS_IsException(symbol)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, symbol); + napi_clear_last_error(env); + return napi_ok; +} + +// Reference management +napi_status napi_create_reference(napi_env env, napi_value value, uint32_t initial_refcount, napi_ref* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + RefInfo* info = new RefInfo{ JS_DupValue(env->context, jsValue), initial_refcount }; + + *result = reinterpret_cast(info); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_delete_reference(napi_env env, napi_ref ref) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + + RefInfo* info = reinterpret_cast(ref); + JS_FreeValue(env->context, info->value); + delete info; + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + + RefInfo* info = reinterpret_cast(ref); + info->count++; + + if (result != nullptr) { + *result = info->count; + } + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + + RefInfo* info = reinterpret_cast(ref); + if (info->count > 0) { + info->count--; + } + + if (result != nullptr) { + *result = info->count; + } + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, ref); + CHECK_ARG(env, result); + + RefInfo* info = reinterpret_cast(ref); + + if (info->count == 0) { + *result = nullptr; + } else { + *result = FromJSValue(env, JS_DupValue(env->context, info->value)); + } + + napi_clear_last_error(env); + return napi_ok; +} + +// Handle scopes - QuickJS uses reference counting, so these are mostly no-ops +napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + env->current_scope_start = env->handle_scope_stack.size(); + *result = reinterpret_cast(env->current_scope_start + 1); + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope) { + CHECK_ENV(env); + CHECK_ARG(env, scope); + + // Free all JSValues created in this scope + size_t scope_start = reinterpret_cast(scope) - 1; + + // Call JS_FreeValue on all values in scope + for (size_t i = scope_start; i < env->handle_scope_stack.size(); i++) { + JS_FreeValue(env->context, *env->handle_scope_stack[i]); + } + + // Remove from stack + env->handle_scope_stack.resize(scope_start); + env->current_scope_start = scope_start; + + napi_clear_last_error(env); + return napi_ok; +} + +// Escapeable handle scopes - similar to regular handle scopes for QuickJS +napi_status napi_open_escapable_handle_scope(napi_env env, napi_escapable_handle_scope* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + // Same as regular handle scope for QuickJS + env->current_scope_start = env->handle_scope_stack.size(); + *result = reinterpret_cast(env->current_scope_start + 1); + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_close_escapable_handle_scope(napi_env env, napi_escapable_handle_scope scope) { + CHECK_ENV(env); + CHECK_ARG(env, scope); + + // Same cleanup as regular handle scope + size_t scope_start = reinterpret_cast(scope) - 1; + + for (size_t i = scope_start; i < env->handle_scope_stack.size(); i++) { + JS_FreeValue(env->context, *env->handle_scope_stack[i]); + } + + env->handle_scope_stack.resize(scope_start); + env->current_scope_start = scope_start; + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_escape_handle(napi_env env, napi_escapable_handle_scope scope, napi_value escapee, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, scope); + CHECK_ARG(env, escapee); + CHECK_ARG(env, result); + + // Get the scope start index + size_t scope_start = reinterpret_cast(scope) - 1; + + // Duplicate the JSValue to create a new handle that will outlive the current scope + JSValue jsValue = ToJSValue(escapee); + JSValue escapedValue = JS_DupValue(env->context, jsValue); + + // Store the escaped value in the parent scope (before scope_start) + auto parentPtr = std::make_unique(escapedValue); + napi_value parentHandle = reinterpret_cast(parentPtr.get()); + + // Insert at parent scope position (before current scope) + if (scope_start > 0) { + env->handle_scope_stack.insert( + env->handle_scope_stack.begin() + scope_start, + std::move(parentPtr) + ); + + // Note: Inserting shifts indices, but since we're inserting at scope_start, + // the current scope's start index is now scope_start + 1 + // We need to update current_scope_start if it was pointing to this scope + if (env->current_scope_start == scope_start) { + env->current_scope_start = scope_start + 1; + } + } else { + // No parent scope - just add to the beginning + env->handle_scope_stack.insert( + env->handle_scope_stack.begin(), + std::move(parentPtr) + ); + + if (env->current_scope_start == 0) { + env->current_scope_start = 1; + } + } + + *result = parentHandle; + napi_clear_last_error(env); + return napi_ok; +} + +// ArrayBuffer support +napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + *result = JS_IsArrayBuffer(jsValue); + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_create_arraybuffer(napi_env env, size_t byte_length, void** data, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + JSValue arrayBuffer = JS_NewArrayBufferCopy(env->context, nullptr, byte_length); + + if (JS_IsException(arrayBuffer)) { + return napi_set_last_error(env, napi_generic_failure); + } + + if (data != nullptr) { + size_t size; + *data = JS_GetArrayBuffer(env->context, &size, arrayBuffer); + } + + *result = FromJSValue(env, arrayBuffer); + napi_clear_last_error(env); + return napi_ok; +} + +namespace { + void ArrayBufferFreeCallback(JSRuntime* rt, void* opaque, void* ptr) { + ExternalData* externalData = reinterpret_cast(opaque); + if (externalData != nullptr) { + if (externalData->Data() != nullptr) { + // Call the user's finalize callback + delete externalData; + } + } + } +} + +napi_status napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, napi_finalize finalize_cb, void* finalize_hint, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + ExternalData* externalDataInfo = new ExternalData(env, external_data, finalize_cb, finalize_hint); + + JSValue arrayBuffer = JS_NewArrayBuffer(env->context, + reinterpret_cast(external_data), + byte_length, + ArrayBufferFreeCallback, + externalDataInfo, + 0); + + if (JS_IsException(arrayBuffer)) { + delete externalDataInfo; + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, arrayBuffer); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_get_arraybuffer_info(napi_env env, napi_value arraybuffer, void** data, size_t* byte_length) { + CHECK_ENV(env); + CHECK_ARG(env, arraybuffer); + + JSValue jsArrayBuffer = ToJSValue(arraybuffer); + size_t size; + uint8_t* bufferData = JS_GetArrayBuffer(env->context, &size, jsArrayBuffer); + + if (data != nullptr) { + *data = bufferData; + } + + if (byte_length != nullptr) { + *byte_length = size; + } + + napi_clear_last_error(env); + return napi_ok; +} + +// TypedArray support +napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + JSValue jsValue = ToJSValue(value); + *result = JS_GetTypedArrayType(jsValue) != -1; + + napi_clear_last_error(env); + return napi_ok; +} + +// TypedArray support +napi_status napi_create_typedarray(napi_env env, napi_typedarray_type type, size_t length, napi_value arraybuffer, size_t byte_offset, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, arraybuffer); + CHECK_ARG(env, result); + + JSTypedArrayEnum jsType; + switch (type) { + case napi_int8_array: jsType = JS_TYPED_ARRAY_INT8; break; + case napi_uint8_array: jsType = JS_TYPED_ARRAY_UINT8; break; + case napi_uint8_clamped_array: jsType = JS_TYPED_ARRAY_UINT8C; break; + case napi_int16_array: jsType = JS_TYPED_ARRAY_INT16; break; + case napi_uint16_array: jsType = JS_TYPED_ARRAY_UINT16; break; + case napi_int32_array: jsType = JS_TYPED_ARRAY_INT32; break; + case napi_uint32_array: jsType = JS_TYPED_ARRAY_UINT32; break; + case napi_float32_array: jsType = JS_TYPED_ARRAY_FLOAT32; break; + case napi_float64_array: jsType = JS_TYPED_ARRAY_FLOAT64; break; + case napi_bigint64_array: jsType = JS_TYPED_ARRAY_BIG_INT64; break; + case napi_biguint64_array: jsType = JS_TYPED_ARRAY_BIG_UINT64; break; + default: + return napi_set_last_error(env, napi_invalid_arg); + } + + JSValue jsArrayBuffer = ToJSValue(arraybuffer); + + // Create temporary JSValue arguments + JSValue offsetVal = JS_NewInt64(env->context, byte_offset); + JSValue lengthVal = JS_NewInt64(env->context, length); + + JSValueConst args[3] = { jsArrayBuffer, offsetVal, lengthVal }; + JSValue typedArray = JS_NewTypedArray(env->context, 3, args, jsType); + + // Free temporary values + JS_FreeValue(env->context, offsetVal); + JS_FreeValue(env->context, lengthVal); + + if (JS_IsException(typedArray)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, typedArray); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_create_dataview(napi_env env, size_t byte_length, napi_value arraybuffer, size_t byte_offset, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, arraybuffer); + CHECK_ARG(env, result); + + JSValue global = JS_GetGlobalObject(env->context); + JSValue dataViewCtor = JS_GetPropertyStr(env->context, global, "DataView"); + + JSValue jsArrayBuffer = ToJSValue(arraybuffer); + + // Create temporary JSValue arguments + JSValue offsetVal = JS_NewInt64(env->context, byte_offset); + JSValue lengthVal = JS_NewInt64(env->context, byte_length); + + JSValueConst args[3] = { jsArrayBuffer, offsetVal, lengthVal }; + JSValue dataView = JS_CallConstructor(env->context, dataViewCtor, 3, args); + + // Free temporary values + JS_FreeValue(env->context, offsetVal); + JS_FreeValue(env->context, lengthVal); + JS_FreeValue(env->context, dataViewCtor); + JS_FreeValue(env->context, global); + + if (JS_IsException(dataView)) { + return napi_set_last_error(env, napi_generic_failure); + } + + *result = FromJSValue(env, dataView); + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_get_typedarray_info(napi_env env, napi_value typedarray, napi_typedarray_type* type, size_t* length, void** data, napi_value* arraybuffer, size_t* byte_offset) { + CHECK_ENV(env); + CHECK_ARG(env, typedarray); + + JSValue jsTypedArray = ToJSValue(typedarray); + + size_t offset, byteLength, bytesPerElement; + JSValue buffer = JS_GetTypedArrayBuffer(env->context, jsTypedArray, &offset, &byteLength, &bytesPerElement); + + if (JS_IsException(buffer)) { + return napi_set_last_error(env, napi_generic_failure); + } + + if (byte_offset != nullptr) { + *byte_offset = offset; + } + + if (length != nullptr) { + *length = byteLength / bytesPerElement; + } + + if (arraybuffer != nullptr) { + *arraybuffer = FromJSValue(env, buffer); + } else { + JS_FreeValue(env->context, buffer); + } + + if (data != nullptr) { + size_t bufferSize; + uint8_t* bufferData = JS_GetArrayBuffer(env->context, &bufferSize, buffer); + *data = bufferData + offset; + } + + if (type != nullptr) { + // Determine type from bytes per element + switch (bytesPerElement) { + case 1: *type = napi_uint8_array; break; + case 2: *type = napi_uint16_array; break; + case 4: *type = napi_uint32_array; break; + case 8: *type = napi_float64_array; break; + default: *type = napi_uint8_array; break; + } + } + + napi_clear_last_error(env); + return napi_ok; +} + +// DataView support +napi_status napi_is_dataview(napi_env env, napi_value value, bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + + // QuickJS doesn't have a direct way to check DataView, so use instanceof + JSValue jsValue = ToJSValue(value); + JSValue global = JS_GetGlobalObject(env->context); + JSValue dataViewCtor = JS_GetPropertyStr(env->context, global, "DataView"); + + int ret = JS_IsInstanceOf(env->context, jsValue, dataViewCtor); + *result = (ret > 0); + + JS_FreeValue(env->context, dataViewCtor); + JS_FreeValue(env->context, global); + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_get_dataview_info(napi_env env, napi_value dataview, size_t* byte_length, void** data, napi_value* arraybuffer, size_t* byte_offset) { + CHECK_ENV(env); + CHECK_ARG(env, dataview); + + JSValue jsDataView = ToJSValue(dataview); + + // Get buffer property + JSValue buffer = JS_GetPropertyStr(env->context, jsDataView, "buffer"); + JSValue byteOffsetVal = JS_GetPropertyStr(env->context, jsDataView, "byteOffset"); + JSValue byteLengthVal = JS_GetPropertyStr(env->context, jsDataView, "byteLength"); + + if (byte_offset != nullptr) { + int64_t offset; + JS_ToInt64(env->context, &offset, byteOffsetVal); + *byte_offset = static_cast(offset); + } + + if (byte_length != nullptr) { + int64_t length; + JS_ToInt64(env->context, &length, byteLengthVal); + *byte_length = static_cast(length); + } + + if (arraybuffer != nullptr) { + *arraybuffer = FromJSValue(env, JS_DupValue(env->context, buffer)); + } + + if (data != nullptr) { + size_t bufferSize; + uint8_t* bufferData = JS_GetArrayBuffer(env->context, &bufferSize, buffer); + int64_t offset; + JS_ToInt64(env->context, &offset, byteOffsetVal); + *data = bufferData + offset; + } + + JS_FreeValue(env->context, buffer); + JS_FreeValue(env->context, byteOffsetVal); + JS_FreeValue(env->context, byteLengthVal); + + napi_clear_last_error(env); + return napi_ok; +} + +// Version +napi_status napi_get_version(napi_env env, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = NAPI_VERSION; + napi_clear_last_error(env); + return napi_ok; +} + +// Promise support +napi_status napi_create_promise(napi_env env, napi_deferred* deferred, napi_value* promise) { + CHECK_ENV(env); + CHECK_ARG(env, deferred); + CHECK_ARG(env, promise); + + JSValue resolving_funcs[2]; + JSValue jsPromise = JS_NewPromiseCapability(env->context, resolving_funcs); + + if (JS_IsException(jsPromise)) { + return napi_set_last_error(env, napi_generic_failure); + } + + // Store resolve/reject functions in a container object + JSValue container = JS_NewObject(env->context); + JS_SetPropertyStr(env->context, container, "resolve", resolving_funcs[0]); + JS_SetPropertyStr(env->context, container, "reject", resolving_funcs[1]); + + // Create reference directly from container WITHOUT going through FromJSValue + // to avoid double-ownership (handle scope + reference) + RefInfo* refInfo = new RefInfo{ container, 1 }; // Use refcount 1 for immediate access + napi_ref ref = reinterpret_cast(refInfo); + + *deferred = reinterpret_cast(ref); + *promise = FromJSValue(env, jsPromise); + + napi_clear_last_error(env); + return napi_ok; +} + +napi_status napi_resolve_deferred(napi_env env, napi_deferred deferred, napi_value resolution) { + CHECK_ENV(env); + CHECK_ARG(env, deferred); + CHECK_ARG(env, resolution); + + napi_ref ref = reinterpret_cast(deferred); + napi_value container; + CHECK_NAPI(napi_get_reference_value(env, ref, &container)); + + if (container == nullptr) { + return napi_set_last_error(env, napi_invalid_arg); + } + + napi_value resolve; + CHECK_NAPI(napi_get_named_property(env, container, "resolve", &resolve)); + + napi_value undefined; + CHECK_NAPI(napi_get_undefined(env, &undefined)); + CHECK_NAPI(napi_call_function(env, undefined, resolve, 1, &resolution, nullptr)); + + // Clean up the reference + CHECK_NAPI(napi_delete_reference(env, ref)); + + return napi_ok; +} + +napi_status napi_reject_deferred(napi_env env, napi_deferred deferred, napi_value rejection) { + CHECK_ENV(env); + CHECK_ARG(env, deferred); + CHECK_ARG(env, rejection); + + napi_ref ref = reinterpret_cast(deferred); + napi_value container; + CHECK_NAPI(napi_get_reference_value(env, ref, &container)); + + if (container == nullptr) { + return napi_set_last_error(env, napi_invalid_arg); + } + + napi_value reject; + CHECK_NAPI(napi_get_named_property(env, container, "reject", &reject)); + + napi_value undefined; + CHECK_NAPI(napi_get_undefined(env, &undefined)); + CHECK_NAPI(napi_call_function(env, undefined, reject, 1, &rejection, nullptr)); + + // Clean up the reference + CHECK_NAPI(napi_delete_reference(env, ref)); + + return napi_ok; +} + +napi_status napi_is_promise(napi_env env, napi_value value, bool* is_promise) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, is_promise); + + napi_value global, promise_ctor; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI(napi_get_named_property(env, global, "Promise", &promise_ctor)); + CHECK_NAPI(napi_instanceof(env, value, promise_ctor, is_promise)); + + return napi_ok; +} + +// Script execution +napi_status napi_run_script(napi_env env, napi_value script, napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, script); + CHECK_ARG(env, result); + + JSValue jsScript = ToJSValue(script); + + size_t len; + const char* str = JS_ToCStringLen(env->context, &len, jsScript); + if (!str) { + return napi_set_last_error(env, napi_string_expected); + } + + JSValue jsResult = JS_Eval(env->context, str, len, "