nodepyx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +399 -0
- package/binding.gyp +73 -0
- package/dist/core/PyCallable.d.ts +65 -0
- package/dist/core/PyCallable.d.ts.map +1 -0
- package/dist/core/PyCallable.js +109 -0
- package/dist/core/PyCallable.js.map +1 -0
- package/dist/core/PyContext.d.ts +76 -0
- package/dist/core/PyContext.d.ts.map +1 -0
- package/dist/core/PyContext.js +228 -0
- package/dist/core/PyContext.js.map +1 -0
- package/dist/core/PyIterator.d.ts +84 -0
- package/dist/core/PyIterator.d.ts.map +1 -0
- package/dist/core/PyIterator.js +243 -0
- package/dist/core/PyIterator.js.map +1 -0
- package/dist/core/PyModule.d.ts +55 -0
- package/dist/core/PyModule.d.ts.map +1 -0
- package/dist/core/PyModule.js +172 -0
- package/dist/core/PyModule.js.map +1 -0
- package/dist/core/PyProxy.d.ts +65 -0
- package/dist/core/PyProxy.d.ts.map +1 -0
- package/dist/core/PyProxy.js +483 -0
- package/dist/core/PyProxy.js.map +1 -0
- package/dist/core/PyRuntime.d.ts +105 -0
- package/dist/core/PyRuntime.d.ts.map +1 -0
- package/dist/core/PyRuntime.js +438 -0
- package/dist/core/PyRuntime.js.map +1 -0
- package/dist/env/CondaManager.d.ts +118 -0
- package/dist/env/CondaManager.d.ts.map +1 -0
- package/dist/env/CondaManager.js +401 -0
- package/dist/env/CondaManager.js.map +1 -0
- package/dist/env/PackageInstaller.d.ts +233 -0
- package/dist/env/PackageInstaller.d.ts.map +1 -0
- package/dist/env/PackageInstaller.js +609 -0
- package/dist/env/PackageInstaller.js.map +1 -0
- package/dist/env/PythonDetector.d.ts +103 -0
- package/dist/env/PythonDetector.d.ts.map +1 -0
- package/dist/env/PythonDetector.js +381 -0
- package/dist/env/PythonDetector.js.map +1 -0
- package/dist/env/VenvManager.d.ts +117 -0
- package/dist/env/VenvManager.d.ts.map +1 -0
- package/dist/env/VenvManager.js +331 -0
- package/dist/env/VenvManager.js.map +1 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +393 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/Plugin.interface.d.ts +41 -0
- package/dist/plugins/Plugin.interface.d.ts.map +1 -0
- package/dist/plugins/Plugin.interface.js +12 -0
- package/dist/plugins/Plugin.interface.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +26 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +174 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/builtin/NumpyPlugin.d.ts +17 -0
- package/dist/plugins/builtin/NumpyPlugin.d.ts.map +1 -0
- package/dist/plugins/builtin/NumpyPlugin.js +41 -0
- package/dist/plugins/builtin/NumpyPlugin.js.map +1 -0
- package/dist/plugins/builtin/PandasPlugin.d.ts +19 -0
- package/dist/plugins/builtin/PandasPlugin.d.ts.map +1 -0
- package/dist/plugins/builtin/PandasPlugin.js +57 -0
- package/dist/plugins/builtin/PandasPlugin.js.map +1 -0
- package/dist/plugins/builtin/TorchPlugin.d.ts +23 -0
- package/dist/plugins/builtin/TorchPlugin.d.ts.map +1 -0
- package/dist/plugins/builtin/TorchPlugin.js +50 -0
- package/dist/plugins/builtin/TorchPlugin.js.map +1 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +12 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/serialization/DataFrameBridge.d.ts +141 -0
- package/dist/serialization/DataFrameBridge.d.ts.map +1 -0
- package/dist/serialization/DataFrameBridge.js +355 -0
- package/dist/serialization/DataFrameBridge.js.map +1 -0
- package/dist/serialization/MsgPackSerializer.d.ts +45 -0
- package/dist/serialization/MsgPackSerializer.d.ts.map +1 -0
- package/dist/serialization/MsgPackSerializer.js +242 -0
- package/dist/serialization/MsgPackSerializer.js.map +1 -0
- package/dist/serialization/NumpyBridge.d.ts +96 -0
- package/dist/serialization/NumpyBridge.d.ts.map +1 -0
- package/dist/serialization/NumpyBridge.js +323 -0
- package/dist/serialization/NumpyBridge.js.map +1 -0
- package/dist/serialization/Serializer.d.ts +78 -0
- package/dist/serialization/Serializer.d.ts.map +1 -0
- package/dist/serialization/Serializer.js +281 -0
- package/dist/serialization/Serializer.js.map +1 -0
- package/dist/types/PythonTypeMapper.d.ts +87 -0
- package/dist/types/PythonTypeMapper.d.ts.map +1 -0
- package/dist/types/PythonTypeMapper.js +449 -0
- package/dist/types/PythonTypeMapper.js.map +1 -0
- package/dist/types/StubCache.d.ts +109 -0
- package/dist/types/StubCache.d.ts.map +1 -0
- package/dist/types/StubCache.js +333 -0
- package/dist/types/StubCache.js.map +1 -0
- package/dist/types/TypeGenerator.d.ts +139 -0
- package/dist/types/TypeGenerator.d.ts.map +1 -0
- package/dist/types/TypeGenerator.js +372 -0
- package/dist/types/TypeGenerator.js.map +1 -0
- package/dist/types/addon.d.ts +114 -0
- package/dist/types/addon.d.ts.map +1 -0
- package/dist/types/addon.js +32 -0
- package/dist/types/addon.js.map +1 -0
- package/dist/types/config.d.ts +175 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +35 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/python.d.ts +235 -0
- package/dist/types/python.d.ts.map +1 -0
- package/dist/types/python.js +7 -0
- package/dist/types/python.js.map +1 -0
- package/dist/utils/ErrorTranslator.d.ts +83 -0
- package/dist/utils/ErrorTranslator.d.ts.map +1 -0
- package/dist/utils/ErrorTranslator.js +210 -0
- package/dist/utils/ErrorTranslator.js.map +1 -0
- package/dist/utils/Logger.d.ts +27 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +115 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/MemoryMonitor.d.ts +44 -0
- package/dist/utils/MemoryMonitor.d.ts.map +1 -0
- package/dist/utils/MemoryMonitor.js +143 -0
- package/dist/utils/MemoryMonitor.js.map +1 -0
- package/package.json +177 -0
- package/python/error_handler.py +433 -0
- package/python/nodepyx_runtime.py +575 -0
- package/python/serializer.py +379 -0
- package/python/type_inspector.py +288 -0
- package/scripts/build-native.js +68 -0
- package/scripts/download-prebuilds.js +99 -0
- package/scripts/generate-stubs.js +405 -0
- package/scripts/install.js +260 -0
- package/src/core/PyCallable.ts +137 -0
- package/src/core/PyContext.ts +296 -0
- package/src/core/PyIterator.ts +294 -0
- package/src/core/PyModule.ts +194 -0
- package/src/core/PyProxy.ts +605 -0
- package/src/core/PyRuntime.ts +504 -0
- package/src/env/CondaManager.ts +451 -0
- package/src/env/PackageInstaller.ts +738 -0
- package/src/env/PythonDetector.ts +414 -0
- package/src/env/VenvManager.ts +396 -0
- package/src/index.ts +425 -0
- package/src/native/gil_guard.cpp +26 -0
- package/src/native/gil_guard.h +175 -0
- package/src/native/nodepyx_addon.cpp +886 -0
- package/src/native/python_bridge.cpp +790 -0
- package/src/native/python_bridge.h +257 -0
- package/src/native/thread_pool.cpp +336 -0
- package/src/native/thread_pool.h +175 -0
- package/src/native/type_converter.cpp +901 -0
- package/src/native/type_converter.h +272 -0
- package/src/nextjs/PyProvider.tsx +123 -0
- package/src/nextjs/index.ts +21 -0
- package/src/nextjs/usePython.ts +106 -0
- package/src/nextjs/withnodepyx.ts +88 -0
- package/src/plugins/Plugin.interface.ts +51 -0
- package/src/plugins/PluginManager.ts +155 -0
- package/src/plugins/builtin/NumpyPlugin.ts +36 -0
- package/src/plugins/builtin/PandasPlugin.ts +49 -0
- package/src/plugins/builtin/TorchPlugin.ts +56 -0
- package/src/plugins/index.ts +7 -0
- package/src/serialization/DataFrameBridge.ts +398 -0
- package/src/serialization/MsgPackSerializer.ts +220 -0
- package/src/serialization/NumpyBridge.ts +332 -0
- package/src/serialization/Serializer.ts +320 -0
- package/src/types/PythonTypeMapper.ts +495 -0
- package/src/types/StubCache.ts +340 -0
- package/src/types/TypeGenerator.ts +491 -0
- package/src/types/addon.ts +170 -0
- package/src/types/config.ts +226 -0
- package/src/types/index.ts +55 -0
- package/src/types/python.ts +309 -0
- package/src/types/stubs/numpy.d.ts +441 -0
- package/src/types/stubs/pandas.d.ts +575 -0
- package/src/types/stubs/sklearn.d.ts +728 -0
- package/src/types/stubs/torch.d.ts +694 -0
- package/src/utils/ErrorTranslator.ts +220 -0
- package/src/utils/Logger.ts +119 -0
- package/src/utils/MemoryMonitor.ts +175 -0
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nodepyx — N-API Addon Entry Point
|
|
3
|
+
*
|
|
4
|
+
* This file is the bridge between Node.js (via N-API) and the Python C API.
|
|
5
|
+
* It exports all functions that the TypeScript layer calls.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Synchronous operations → directly call PythonBridge::*Sync() in a worker thread
|
|
9
|
+
* - All Python work happens in worker threads (never on the JS event loop)
|
|
10
|
+
* - Results flow back via ThreadSafeFunction callbacks
|
|
11
|
+
* - Memory management via TypeConverter::registerObject/decRef
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#define NAPI_VERSION 8
|
|
15
|
+
#define PY_SSIZE_T_CLEAN
|
|
16
|
+
#include <napi.h>
|
|
17
|
+
#include <Python.h>
|
|
18
|
+
|
|
19
|
+
#include "python_bridge.h"
|
|
20
|
+
#include "thread_pool.h"
|
|
21
|
+
#include "type_converter.h"
|
|
22
|
+
#include "gil_guard.h"
|
|
23
|
+
|
|
24
|
+
#include <memory>
|
|
25
|
+
#include <string>
|
|
26
|
+
#include <vector>
|
|
27
|
+
#include <map>
|
|
28
|
+
#include <sstream>
|
|
29
|
+
#include <stdexcept>
|
|
30
|
+
#include <iostream>
|
|
31
|
+
#include <chrono>
|
|
32
|
+
#include <ctime>
|
|
33
|
+
|
|
34
|
+
namespace nodepyx {
|
|
35
|
+
|
|
36
|
+
// ─── Global Thread Pool ──────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
static std::unique_ptr<PythonThreadPool> g_threadPool;
|
|
39
|
+
static bool g_initialized = false;
|
|
40
|
+
|
|
41
|
+
// ─── Helper: Extract SerializedValue from JS object ─────────────────────────
|
|
42
|
+
|
|
43
|
+
static SerializedValue jsToSerializedValue(const Napi::Object& obj) {
|
|
44
|
+
SerializedValue sv;
|
|
45
|
+
|
|
46
|
+
if (!obj.Has("format")) {
|
|
47
|
+
sv.format = SerializedFormat::NONE;
|
|
48
|
+
return sv;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
int32_t fmt = obj.Get("format").As<Napi::Number>().Int32Value();
|
|
52
|
+
sv.format = static_cast<SerializedFormat>(fmt);
|
|
53
|
+
|
|
54
|
+
if (obj.Has("data")) {
|
|
55
|
+
Napi::Value data = obj.Get("data");
|
|
56
|
+
if (data.IsString()) {
|
|
57
|
+
sv.jsonData = data.As<Napi::String>().Utf8Value();
|
|
58
|
+
} else if (data.IsBuffer()) {
|
|
59
|
+
Napi::Buffer<uint8_t> buf = data.As<Napi::Buffer<uint8_t>>();
|
|
60
|
+
sv.binaryData.assign(buf.Data(), buf.Data() + buf.Length());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (obj.Has("metadata") && obj.Get("metadata").IsObject()) {
|
|
65
|
+
Napi::Object meta = obj.Get("metadata").As<Napi::Object>();
|
|
66
|
+
if (meta.Has("objectId")) {
|
|
67
|
+
sv.metadata.objectId = meta.Get("objectId").As<Napi::Number>().Uint32Value();
|
|
68
|
+
}
|
|
69
|
+
if (meta.Has("dtype") && meta.Get("dtype").IsString()) {
|
|
70
|
+
sv.metadata.dtype = meta.Get("dtype").As<Napi::String>().Utf8Value();
|
|
71
|
+
}
|
|
72
|
+
if (meta.Has("shape") && meta.Get("shape").IsArray()) {
|
|
73
|
+
Napi::Array shape = meta.Get("shape").As<Napi::Array>();
|
|
74
|
+
for (uint32_t i = 0; i < shape.Length(); ++i) {
|
|
75
|
+
sv.metadata.shape.push_back(shape.Get(i).As<Napi::Number>().Int64Value());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return sv;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Helper: Convert TaskResult to JS object ─────────────────────────────────
|
|
84
|
+
|
|
85
|
+
static Napi::Object taskResultToJS(Napi::Env env, const TaskResult& result) {
|
|
86
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
87
|
+
obj.Set("success", Napi::Boolean::New(env, result.success));
|
|
88
|
+
|
|
89
|
+
if (result.success) {
|
|
90
|
+
// Parse result JSON to return as JS value
|
|
91
|
+
if (!result.resultJson.empty() && result.resultJson != "null") {
|
|
92
|
+
// Return raw JSON string to be parsed in TypeScript
|
|
93
|
+
obj.Set("resultJson", Napi::String::New(env, result.resultJson));
|
|
94
|
+
} else {
|
|
95
|
+
obj.Set("resultJson", env.Null());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (result.isObject) {
|
|
99
|
+
obj.Set("objectId", Napi::Number::New(env, result.objectId));
|
|
100
|
+
obj.Set("isObject", Napi::Boolean::New(env, true));
|
|
101
|
+
} else {
|
|
102
|
+
obj.Set("isObject", Napi::Boolean::New(env, false));
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
Napi::Object error = Napi::Object::New(env);
|
|
106
|
+
error.Set("type", Napi::String::New(env, result.errorType));
|
|
107
|
+
error.Set("message", Napi::String::New(env, result.errorMessage));
|
|
108
|
+
error.Set("traceback", Napi::String::New(env, result.errorTraceback));
|
|
109
|
+
obj.Set("error", error);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return obj;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Exported Functions ──────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* initializePython(options) → Promise<void>
|
|
119
|
+
* Initialize the CPython interpreter with given options.
|
|
120
|
+
*/
|
|
121
|
+
static Napi::Value InitializePython(const Napi::CallbackInfo& info) {
|
|
122
|
+
Napi::Env env = info.Env();
|
|
123
|
+
|
|
124
|
+
if (g_initialized) {
|
|
125
|
+
return env.Undefined();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
129
|
+
Napi::TypeError::New(env, "initializePython expects an options object").ThrowAsJavaScriptException();
|
|
130
|
+
return env.Undefined();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Napi::Object opts = info[0].As<Napi::Object>();
|
|
134
|
+
|
|
135
|
+
std::string pythonHome = opts.Has("pythonHome") ?
|
|
136
|
+
opts.Get("pythonHome").As<Napi::String>().Utf8Value() : "";
|
|
137
|
+
|
|
138
|
+
std::string pythonExecutable = opts.Has("pythonExecutable") ?
|
|
139
|
+
opts.Get("pythonExecutable").As<Napi::String>().Utf8Value() : "python3";
|
|
140
|
+
|
|
141
|
+
uint32_t threadPoolSize = opts.Has("threadPoolSize") ?
|
|
142
|
+
opts.Get("threadPoolSize").As<Napi::Number>().Uint32Value() : 4;
|
|
143
|
+
|
|
144
|
+
uint32_t maxQueueSize = opts.Has("maxQueueSize") ?
|
|
145
|
+
opts.Get("maxQueueSize").As<Napi::Number>().Uint32Value() : 1000;
|
|
146
|
+
|
|
147
|
+
uint32_t callTimeout = opts.Has("callTimeout") ?
|
|
148
|
+
opts.Get("callTimeout").As<Napi::Number>().Uint32Value() : 30000;
|
|
149
|
+
|
|
150
|
+
std::vector<std::string> pythonPathExtra;
|
|
151
|
+
if (opts.Has("pythonPathExtra") && opts.Get("pythonPathExtra").IsArray()) {
|
|
152
|
+
Napi::Array paths = opts.Get("pythonPathExtra").As<Napi::Array>();
|
|
153
|
+
for (uint32_t i = 0; i < paths.Length(); ++i) {
|
|
154
|
+
pythonPathExtra.push_back(paths.Get(i).As<Napi::String>().Utf8Value());
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
std::vector<std::pair<std::string, std::string>> envVars;
|
|
159
|
+
if (opts.Has("env") && opts.Get("env").IsObject()) {
|
|
160
|
+
Napi::Object envObj = opts.Get("env").As<Napi::Object>();
|
|
161
|
+
Napi::Array keys = envObj.GetPropertyNames();
|
|
162
|
+
for (uint32_t i = 0; i < keys.Length(); ++i) {
|
|
163
|
+
std::string key = keys.Get(i).As<Napi::String>().Utf8Value();
|
|
164
|
+
std::string val = envObj.Get(key).As<Napi::String>().Utf8Value();
|
|
165
|
+
envVars.push_back({key, val});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Return a Promise
|
|
170
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
171
|
+
|
|
172
|
+
// Initialize Python synchronously (must happen on main thread)
|
|
173
|
+
try {
|
|
174
|
+
PythonBridge::getInstance().initializePython(
|
|
175
|
+
pythonHome,
|
|
176
|
+
pythonExecutable,
|
|
177
|
+
pythonPathExtra,
|
|
178
|
+
envVars
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Initialize thread pool
|
|
182
|
+
g_threadPool = std::make_unique<PythonThreadPool>(threadPoolSize, maxQueueSize);
|
|
183
|
+
g_threadPool->initialize(env);
|
|
184
|
+
|
|
185
|
+
g_initialized = true;
|
|
186
|
+
deferred.Resolve(env.Undefined());
|
|
187
|
+
} catch (const std::exception& e) {
|
|
188
|
+
deferred.Reject(Napi::Error::New(env, e.what()).Value());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return deferred.Promise();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* finalizePython() → Promise<void>
|
|
196
|
+
*/
|
|
197
|
+
static Napi::Value FinalizePython(const Napi::CallbackInfo& info) {
|
|
198
|
+
Napi::Env env = info.Env();
|
|
199
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
200
|
+
|
|
201
|
+
if (!g_initialized) {
|
|
202
|
+
deferred.Resolve(env.Undefined());
|
|
203
|
+
return deferred.Promise();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
if (g_threadPool) {
|
|
208
|
+
g_threadPool->shutdown();
|
|
209
|
+
g_threadPool.reset();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
PythonBridge::getInstance().finalizePython();
|
|
213
|
+
g_initialized = false;
|
|
214
|
+
deferred.Resolve(env.Undefined());
|
|
215
|
+
} catch (const std::exception& e) {
|
|
216
|
+
deferred.Reject(Napi::Error::New(env, e.what()).Value());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return deferred.Promise();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* isInitialized() → boolean
|
|
224
|
+
*/
|
|
225
|
+
static Napi::Value IsInitialized(const Napi::CallbackInfo& info) {
|
|
226
|
+
return Napi::Boolean::New(info.Env(), g_initialized);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─── Generic async wrapper ──────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Submits a task to the thread pool and returns a Promise.
|
|
233
|
+
* The work function runs with the GIL held in a worker thread.
|
|
234
|
+
*/
|
|
235
|
+
static Napi::Value SubmitTask(
|
|
236
|
+
Napi::Env env,
|
|
237
|
+
std::function<TaskResult()> work,
|
|
238
|
+
uint32_t timeoutMs = 0
|
|
239
|
+
) {
|
|
240
|
+
auto deferred = std::make_shared<Napi::Promise::Deferred>(
|
|
241
|
+
Napi::Promise::Deferred::New(env)
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (!g_initialized || !g_threadPool) {
|
|
245
|
+
deferred->Reject(Napi::Error::New(env, "nodepyx not initialized").Value());
|
|
246
|
+
return deferred->Promise();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Capture deferred by shared_ptr to safely use in callback
|
|
250
|
+
g_threadPool->submit(
|
|
251
|
+
std::move(work),
|
|
252
|
+
[deferred, e = Napi::Reference<Napi::Value>::New(env.Global())](TaskResult result) {
|
|
253
|
+
Napi::Env callEnv = deferred->Env();
|
|
254
|
+
Napi::HandleScope scope(callEnv);
|
|
255
|
+
|
|
256
|
+
Napi::Object resultObj = taskResultToJS(callEnv, result);
|
|
257
|
+
|
|
258
|
+
if (result.success) {
|
|
259
|
+
deferred->Resolve(resultObj);
|
|
260
|
+
} else {
|
|
261
|
+
// Create a proper JS Error
|
|
262
|
+
std::string errMsg = "[Python " + result.errorType + "] " + result.errorMessage;
|
|
263
|
+
Napi::Error jsError = Napi::Error::New(callEnv, errMsg);
|
|
264
|
+
jsError.Value().As<Napi::Object>().Set("pythonType",
|
|
265
|
+
Napi::String::New(callEnv, result.errorType));
|
|
266
|
+
jsError.Value().As<Napi::Object>().Set("pythonTraceback",
|
|
267
|
+
Napi::String::New(callEnv, result.errorTraceback));
|
|
268
|
+
deferred->Reject(jsError.Value());
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
timeoutMs
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
return deferred->Promise();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* importModule(moduleName) → Promise<{objectId, success}>
|
|
279
|
+
*/
|
|
280
|
+
static Napi::Value ImportModule(const Napi::CallbackInfo& info) {
|
|
281
|
+
Napi::Env env = info.Env();
|
|
282
|
+
|
|
283
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
284
|
+
Napi::TypeError::New(env, "importModule expects a string").ThrowAsJavaScriptException();
|
|
285
|
+
return env.Undefined();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
std::string moduleName = info[0].As<Napi::String>().Utf8Value();
|
|
289
|
+
|
|
290
|
+
return SubmitTask(env, [moduleName]() -> TaskResult {
|
|
291
|
+
return PythonBridge::getInstance().importModuleSync(moduleName);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* reloadModule(objectId) → Promise<result>
|
|
297
|
+
*/
|
|
298
|
+
static Napi::Value ReloadModule(const Napi::CallbackInfo& info) {
|
|
299
|
+
Napi::Env env = info.Env();
|
|
300
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
301
|
+
|
|
302
|
+
return SubmitTask(env, [objectId]() -> TaskResult {
|
|
303
|
+
return PythonBridge::getInstance().reloadModuleSync(objectId);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* getAttribute(objectId, name) → Promise<result>
|
|
309
|
+
*/
|
|
310
|
+
static Napi::Value GetAttribute(const Napi::CallbackInfo& info) {
|
|
311
|
+
Napi::Env env = info.Env();
|
|
312
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
313
|
+
std::string attrName = info[1].As<Napi::String>().Utf8Value();
|
|
314
|
+
|
|
315
|
+
return SubmitTask(env, [objectId, attrName]() -> TaskResult {
|
|
316
|
+
return PythonBridge::getInstance().getAttributeSync(objectId, attrName);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* getAttributePath(objectId, path[]) → Promise<result>
|
|
322
|
+
*/
|
|
323
|
+
static Napi::Value GetAttributePath(const Napi::CallbackInfo& info) {
|
|
324
|
+
Napi::Env env = info.Env();
|
|
325
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
326
|
+
|
|
327
|
+
std::vector<std::string> path;
|
|
328
|
+
if (info[1].IsArray()) {
|
|
329
|
+
Napi::Array pathArr = info[1].As<Napi::Array>();
|
|
330
|
+
for (uint32_t i = 0; i < pathArr.Length(); ++i) {
|
|
331
|
+
path.push_back(pathArr.Get(i).As<Napi::String>().Utf8Value());
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return SubmitTask(env, [objectId, path]() -> TaskResult {
|
|
336
|
+
return PythonBridge::getInstance().getAttributePathSync(objectId, path);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* setAttribute(objectId, name, value) → Promise<boolean>
|
|
342
|
+
*/
|
|
343
|
+
static Napi::Value SetAttribute(const Napi::CallbackInfo& info) {
|
|
344
|
+
Napi::Env env = info.Env();
|
|
345
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
346
|
+
std::string attrName = info[1].As<Napi::String>().Utf8Value();
|
|
347
|
+
SerializedValue value = jsToSerializedValue(info[2].As<Napi::Object>());
|
|
348
|
+
|
|
349
|
+
return SubmitTask(env, [objectId, attrName, value]() -> TaskResult {
|
|
350
|
+
return PythonBridge::getInstance().setAttributeSync(objectId, attrName, value);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* hasAttribute(objectId, name) → Promise<boolean>
|
|
356
|
+
*/
|
|
357
|
+
static Napi::Value HasAttribute(const Napi::CallbackInfo& info) {
|
|
358
|
+
Napi::Env env = info.Env();
|
|
359
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
360
|
+
std::string attrName = info[1].As<Napi::String>().Utf8Value();
|
|
361
|
+
|
|
362
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
363
|
+
|
|
364
|
+
if (!g_initialized || !g_threadPool) {
|
|
365
|
+
deferred.Reject(Napi::Error::New(env, "nodepyx not initialized").Value());
|
|
366
|
+
return deferred.Promise();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
auto deferredPtr = std::make_shared<Napi::Promise::Deferred>(std::move(deferred));
|
|
370
|
+
g_threadPool->submit(
|
|
371
|
+
[objectId, attrName]() -> TaskResult {
|
|
372
|
+
GILAcquire gil;
|
|
373
|
+
bool has = PythonBridge::getInstance().hasAttributeSync(objectId, attrName);
|
|
374
|
+
TaskResult r;
|
|
375
|
+
r.success = true;
|
|
376
|
+
r.resultJson = has ? "true" : "false";
|
|
377
|
+
return r;
|
|
378
|
+
},
|
|
379
|
+
[deferredPtr](TaskResult result) {
|
|
380
|
+
Napi::Env callEnv = deferredPtr->Env();
|
|
381
|
+
Napi::HandleScope scope(callEnv);
|
|
382
|
+
bool hasIt = result.resultJson == "true";
|
|
383
|
+
deferredPtr->Resolve(Napi::Boolean::New(callEnv, hasIt));
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
return deferredPtr->Promise();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* deleteAttribute(objectId, name) → Promise<boolean>
|
|
392
|
+
*/
|
|
393
|
+
static Napi::Value DeleteAttribute(const Napi::CallbackInfo& info) {
|
|
394
|
+
Napi::Env env = info.Env();
|
|
395
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
396
|
+
std::string attrName = info[1].As<Napi::String>().Utf8Value();
|
|
397
|
+
|
|
398
|
+
return SubmitTask(env, [objectId, attrName]() -> TaskResult {
|
|
399
|
+
return PythonBridge::getInstance().deleteAttributeSync(objectId, attrName);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* callFunction(objectId, path[], options) → Promise<result>
|
|
405
|
+
*/
|
|
406
|
+
static Napi::Value CallFunction(const Napi::CallbackInfo& info) {
|
|
407
|
+
Napi::Env env = info.Env();
|
|
408
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
409
|
+
|
|
410
|
+
std::vector<std::string> path;
|
|
411
|
+
if (info[1].IsArray()) {
|
|
412
|
+
Napi::Array pathArr = info[1].As<Napi::Array>();
|
|
413
|
+
for (uint32_t i = 0; i < pathArr.Length(); ++i) {
|
|
414
|
+
path.push_back(pathArr.Get(i).As<Napi::String>().Utf8Value());
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
CallOptions options;
|
|
419
|
+
options.timeoutMs = 0;
|
|
420
|
+
|
|
421
|
+
if (info[2].IsObject()) {
|
|
422
|
+
Napi::Object optsObj = info[2].As<Napi::Object>();
|
|
423
|
+
|
|
424
|
+
if (optsObj.Has("args") && optsObj.Get("args").IsArray()) {
|
|
425
|
+
Napi::Array args = optsObj.Get("args").As<Napi::Array>();
|
|
426
|
+
for (uint32_t i = 0; i < args.Length(); ++i) {
|
|
427
|
+
if (args.Get(i).IsObject()) {
|
|
428
|
+
options.args.push_back(jsToSerializedValue(args.Get(i).As<Napi::Object>()));
|
|
429
|
+
} else {
|
|
430
|
+
// Plain value — wrap as JSON
|
|
431
|
+
SerializedValue sv;
|
|
432
|
+
sv.format = SerializedFormat::JSON;
|
|
433
|
+
sv.jsonData = args.Get(i).As<Napi::String>().Utf8Value();
|
|
434
|
+
options.args.push_back(sv);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (optsObj.Has("kwargs") && optsObj.Get("kwargs").IsObject()) {
|
|
440
|
+
Napi::Object kwargs = optsObj.Get("kwargs").As<Napi::Object>();
|
|
441
|
+
Napi::Array kkeys = kwargs.GetPropertyNames();
|
|
442
|
+
for (uint32_t i = 0; i < kkeys.Length(); ++i) {
|
|
443
|
+
std::string key = kkeys.Get(i).As<Napi::String>().Utf8Value();
|
|
444
|
+
if (kwargs.Get(key).IsObject()) {
|
|
445
|
+
options.kwargs.push_back({key, jsToSerializedValue(kwargs.Get(key).As<Napi::Object>())});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (optsObj.Has("timeout") && optsObj.Get("timeout").IsNumber()) {
|
|
451
|
+
options.timeoutMs = optsObj.Get("timeout").As<Napi::Number>().Uint32Value();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return SubmitTask(env, [objectId, path, options]() -> TaskResult {
|
|
456
|
+
return PythonBridge::getInstance().callFunctionSync(objectId, path, options);
|
|
457
|
+
}, options.timeoutMs);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* callMethod(objectId, methodName, options) → Promise<result>
|
|
462
|
+
*/
|
|
463
|
+
static Napi::Value CallMethod(const Napi::CallbackInfo& info) {
|
|
464
|
+
Napi::Env env = info.Env();
|
|
465
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
466
|
+
std::string methodName = info[1].As<Napi::String>().Utf8Value();
|
|
467
|
+
|
|
468
|
+
CallOptions options;
|
|
469
|
+
if (info[2].IsObject()) {
|
|
470
|
+
Napi::Object optsObj = info[2].As<Napi::Object>();
|
|
471
|
+
if (optsObj.Has("args") && optsObj.Get("args").IsArray()) {
|
|
472
|
+
Napi::Array args = optsObj.Get("args").As<Napi::Array>();
|
|
473
|
+
for (uint32_t i = 0; i < args.Length(); ++i) {
|
|
474
|
+
if (args.Get(i).IsObject()) {
|
|
475
|
+
options.args.push_back(jsToSerializedValue(args.Get(i).As<Napi::Object>()));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return SubmitTask(env, [objectId, methodName, options]() -> TaskResult {
|
|
482
|
+
return PythonBridge::getInstance().callMethodSync(objectId, methodName, options);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* getTypeInfo(objectId) → Promise<PyTypeInfo>
|
|
488
|
+
*/
|
|
489
|
+
static Napi::Value GetTypeInfo(const Napi::CallbackInfo& info) {
|
|
490
|
+
Napi::Env env = info.Env();
|
|
491
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
492
|
+
|
|
493
|
+
return SubmitTask(env, [objectId]() -> TaskResult {
|
|
494
|
+
GILAcquire gil;
|
|
495
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
496
|
+
if (!obj) {
|
|
497
|
+
TaskResult r;
|
|
498
|
+
r.success = false;
|
|
499
|
+
r.errorType = "ValueError";
|
|
500
|
+
r.errorMessage = "Object not found";
|
|
501
|
+
return r;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
std::string typeName = TypeConverter::getTypeName(obj);
|
|
505
|
+
std::string qualName = TypeConverter::getQualifiedTypeName(obj);
|
|
506
|
+
bool callable = TypeConverter::isCallable(obj);
|
|
507
|
+
bool iterable = TypeConverter::isIterable(obj);
|
|
508
|
+
bool asyncIterable = TypeConverter::isAsyncIterable(obj);
|
|
509
|
+
bool isModule = TypeConverter::isModule(obj);
|
|
510
|
+
bool isClass = TypeConverter::isClass(obj);
|
|
511
|
+
bool isNone = obj == Py_None;
|
|
512
|
+
|
|
513
|
+
std::ostringstream oss;
|
|
514
|
+
oss << "{"
|
|
515
|
+
<< "\"typeName\":\"" << typeName << "\","
|
|
516
|
+
<< "\"qualName\":\"" << qualName << "\","
|
|
517
|
+
<< "\"isCallable\":" << (callable ? "true" : "false") << ","
|
|
518
|
+
<< "\"isIterable\":" << (iterable ? "true" : "false") << ","
|
|
519
|
+
<< "\"isAsyncIterable\":" << (asyncIterable ? "true" : "false") << ","
|
|
520
|
+
<< "\"isModule\":" << (isModule ? "true" : "false") << ","
|
|
521
|
+
<< "\"isClass\":" << (isClass ? "true" : "false") << ","
|
|
522
|
+
<< "\"isNone\":" << (isNone ? "true" : "false")
|
|
523
|
+
<< "}";
|
|
524
|
+
|
|
525
|
+
TaskResult r;
|
|
526
|
+
r.success = true;
|
|
527
|
+
r.resultJson = oss.str();
|
|
528
|
+
return r;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* getObjectValue(objectId) → Promise<result>
|
|
534
|
+
*/
|
|
535
|
+
static Napi::Value GetObjectValue(const Napi::CallbackInfo& info) {
|
|
536
|
+
Napi::Env env = info.Env();
|
|
537
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
538
|
+
|
|
539
|
+
return SubmitTask(env, [objectId]() -> TaskResult {
|
|
540
|
+
return PythonBridge::getInstance().getObjectValueSync(objectId);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* getObjectRepr(objectId) → Promise<string>
|
|
546
|
+
*/
|
|
547
|
+
static Napi::Value GetObjectRepr(const Napi::CallbackInfo& info) {
|
|
548
|
+
Napi::Env env = info.Env();
|
|
549
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
550
|
+
|
|
551
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
552
|
+
auto deferredPtr = std::make_shared<Napi::Promise::Deferred>(std::move(deferred));
|
|
553
|
+
|
|
554
|
+
if (!g_initialized || !g_threadPool) {
|
|
555
|
+
deferredPtr->Reject(Napi::Error::New(env, "nodepyx not initialized").Value());
|
|
556
|
+
return deferredPtr->Promise();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
g_threadPool->submit(
|
|
560
|
+
[objectId]() -> TaskResult {
|
|
561
|
+
GILAcquire gil;
|
|
562
|
+
std::string repr = PythonBridge::getInstance().getObjectReprSync(objectId);
|
|
563
|
+
TaskResult r;
|
|
564
|
+
r.success = true;
|
|
565
|
+
r.resultJson = "\"" + repr + "\"";
|
|
566
|
+
return r;
|
|
567
|
+
},
|
|
568
|
+
[deferredPtr](TaskResult result) {
|
|
569
|
+
Napi::Env callEnv = deferredPtr->Env();
|
|
570
|
+
Napi::HandleScope scope(callEnv);
|
|
571
|
+
if (result.success) {
|
|
572
|
+
// Strip quotes
|
|
573
|
+
std::string repr = result.resultJson;
|
|
574
|
+
if (repr.size() >= 2 && repr.front() == '"') {
|
|
575
|
+
repr = repr.substr(1, repr.size() - 2);
|
|
576
|
+
}
|
|
577
|
+
deferredPtr->Resolve(Napi::String::New(callEnv, repr));
|
|
578
|
+
} else {
|
|
579
|
+
deferredPtr->Reject(Napi::Error::New(callEnv, result.errorMessage).Value());
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
return deferredPtr->Promise();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* getObjectLength(objectId) → Promise<number>
|
|
589
|
+
*/
|
|
590
|
+
static Napi::Value GetObjectLength(const Napi::CallbackInfo& info) {
|
|
591
|
+
Napi::Env env = info.Env();
|
|
592
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
593
|
+
|
|
594
|
+
return SubmitTask(env, [objectId]() -> TaskResult {
|
|
595
|
+
return PythonBridge::getInstance().getObjectLengthSync(objectId);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* getObjectKeys(objectId) → Promise<string[]>
|
|
601
|
+
*/
|
|
602
|
+
static Napi::Value GetObjectKeys(const Napi::CallbackInfo& info) {
|
|
603
|
+
Napi::Env env = info.Env();
|
|
604
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
605
|
+
|
|
606
|
+
return SubmitTask(env, [objectId]() -> TaskResult {
|
|
607
|
+
return PythonBridge::getInstance().getObjectKeysSync(objectId);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* runPythonCode(code, globals?) → Promise<result>
|
|
613
|
+
*/
|
|
614
|
+
static Napi::Value RunPythonCode(const Napi::CallbackInfo& info) {
|
|
615
|
+
Napi::Env env = info.Env();
|
|
616
|
+
std::string code = info[0].As<Napi::String>().Utf8Value();
|
|
617
|
+
|
|
618
|
+
return SubmitTask(env, [code]() -> TaskResult {
|
|
619
|
+
return PythonBridge::getInstance().runCodeSync(code);
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* evalPython(expression) → Promise<result>
|
|
625
|
+
*/
|
|
626
|
+
static Napi::Value EvalPython(const Napi::CallbackInfo& info) {
|
|
627
|
+
Napi::Env env = info.Env();
|
|
628
|
+
std::string expression = info[0].As<Napi::String>().Utf8Value();
|
|
629
|
+
|
|
630
|
+
return SubmitTask(env, [expression]() -> TaskResult {
|
|
631
|
+
return PythonBridge::getInstance().evalExpressionSync(expression);
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* runPythonFile(filePath) → Promise<result>
|
|
637
|
+
*/
|
|
638
|
+
static Napi::Value RunPythonFile(const Napi::CallbackInfo& info) {
|
|
639
|
+
Napi::Env env = info.Env();
|
|
640
|
+
std::string filePath = info[0].As<Napi::String>().Utf8Value();
|
|
641
|
+
|
|
642
|
+
return SubmitTask(env, [filePath]() -> TaskResult {
|
|
643
|
+
return PythonBridge::getInstance().runFileSync(filePath);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* incRef(objectId) → void
|
|
649
|
+
*/
|
|
650
|
+
static Napi::Value IncRef(const Napi::CallbackInfo& info) {
|
|
651
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
652
|
+
TypeConverter::incRef(objectId);
|
|
653
|
+
return info.Env().Undefined();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* decRef(objectId) → void
|
|
658
|
+
*/
|
|
659
|
+
static Napi::Value DecRef(const Napi::CallbackInfo& info) {
|
|
660
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
661
|
+
TypeConverter::decRef(objectId);
|
|
662
|
+
return info.Env().Undefined();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* collectGarbage() → Promise<void>
|
|
667
|
+
*/
|
|
668
|
+
static Napi::Value CollectGarbage(const Napi::CallbackInfo& info) {
|
|
669
|
+
Napi::Env env = info.Env();
|
|
670
|
+
|
|
671
|
+
return SubmitTask(env, []() -> TaskResult {
|
|
672
|
+
PythonBridge::getInstance().collectGarbage();
|
|
673
|
+
TaskResult r;
|
|
674
|
+
r.success = true;
|
|
675
|
+
r.resultJson = "null";
|
|
676
|
+
return r;
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* getMemoryStats() → AddonMemoryStats (sync)
|
|
682
|
+
*/
|
|
683
|
+
static Napi::Value GetMemoryStats(const Napi::CallbackInfo& info) {
|
|
684
|
+
Napi::Env env = info.Env();
|
|
685
|
+
Napi::Object result = Napi::Object::New(env);
|
|
686
|
+
|
|
687
|
+
size_t trackedObjects = TypeConverter::getRegisteredCount();
|
|
688
|
+
|
|
689
|
+
uint32_t activeThreads = g_threadPool ? g_threadPool->getActiveThreadCount() : 0;
|
|
690
|
+
uint32_t queuedTasks = g_threadPool ? static_cast<uint32_t>(g_threadPool->getQueueSize()) : 0;
|
|
691
|
+
|
|
692
|
+
// Python heap stats (approximate — would need pymalloc hooks for exact)
|
|
693
|
+
result.Set("pythonHeapBytes", Napi::Number::New(env, 0));
|
|
694
|
+
result.Set("pythonHeapPeakBytes", Napi::Number::New(env, 0));
|
|
695
|
+
result.Set("trackedObjects", Napi::Number::New(env, static_cast<double>(trackedObjects)));
|
|
696
|
+
result.Set("pendingCallbacks", Napi::Number::New(env, queuedTasks));
|
|
697
|
+
result.Set("threadPoolActive", Napi::Number::New(env, activeThreads));
|
|
698
|
+
result.Set("threadPoolQueued", Napi::Number::New(env, queuedTasks));
|
|
699
|
+
|
|
700
|
+
return result;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* getProfilingReport() → AddonProfilingReport (sync)
|
|
705
|
+
*/
|
|
706
|
+
static Napi::Value GetProfilingReport(const Napi::CallbackInfo& info) {
|
|
707
|
+
Napi::Env env = info.Env();
|
|
708
|
+
Napi::Object result = Napi::Object::New(env);
|
|
709
|
+
|
|
710
|
+
if (g_threadPool) {
|
|
711
|
+
const auto& stats = g_threadPool->getStats();
|
|
712
|
+
uint64_t total = stats.totalTasksCompleted.load() + stats.totalTasksFailed.load();
|
|
713
|
+
uint64_t totalTimeUs = stats.totalExecutionTimeUs.load();
|
|
714
|
+
double avgMs = total > 0 ? (static_cast<double>(totalTimeUs) / total / 1000.0) : 0.0;
|
|
715
|
+
|
|
716
|
+
result.Set("totalCalls", Napi::Number::New(env, static_cast<double>(total)));
|
|
717
|
+
result.Set("totalTimeMs", Napi::Number::New(env, static_cast<double>(totalTimeUs) / 1000.0));
|
|
718
|
+
result.Set("avgCallTimeMs", Napi::Number::New(env, avgMs));
|
|
719
|
+
result.Set("peakCallTimeMs", Napi::Number::New(env, 0));
|
|
720
|
+
result.Set("callsByModule", Napi::Object::New(env));
|
|
721
|
+
result.Set("callsByFunction", Napi::Object::New(env));
|
|
722
|
+
} else {
|
|
723
|
+
result.Set("totalCalls", Napi::Number::New(env, 0));
|
|
724
|
+
result.Set("totalTimeMs", Napi::Number::New(env, 0));
|
|
725
|
+
result.Set("avgCallTimeMs", Napi::Number::New(env, 0));
|
|
726
|
+
result.Set("peakCallTimeMs", Napi::Number::New(env, 0));
|
|
727
|
+
result.Set("callsByModule", Napi::Object::New(env));
|
|
728
|
+
result.Set("callsByFunction", Napi::Object::New(env));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return result;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* setThreadPoolSize(size) → Promise<void>
|
|
736
|
+
*/
|
|
737
|
+
static Napi::Value SetThreadPoolSize(const Napi::CallbackInfo& info) {
|
|
738
|
+
Napi::Env env = info.Env();
|
|
739
|
+
uint32_t size = info[0].As<Napi::Number>().Uint32Value();
|
|
740
|
+
|
|
741
|
+
if (g_threadPool) {
|
|
742
|
+
g_threadPool->resize(size);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
746
|
+
deferred.Resolve(env.Undefined());
|
|
747
|
+
return deferred.Promise();
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* getThreadPoolStats() → {active, queued, total}
|
|
752
|
+
*/
|
|
753
|
+
static Napi::Value GetThreadPoolStats(const Napi::CallbackInfo& info) {
|
|
754
|
+
Napi::Env env = info.Env();
|
|
755
|
+
Napi::Object result = Napi::Object::New(env);
|
|
756
|
+
|
|
757
|
+
if (g_threadPool) {
|
|
758
|
+
result.Set("active", Napi::Number::New(env, g_threadPool->getActiveThreadCount()));
|
|
759
|
+
result.Set("queued", Napi::Number::New(env, static_cast<double>(g_threadPool->getQueueSize())));
|
|
760
|
+
result.Set("total", Napi::Number::New(env, g_threadPool->getThreadCount()));
|
|
761
|
+
} else {
|
|
762
|
+
result.Set("active", Napi::Number::New(env, 0));
|
|
763
|
+
result.Set("queued", Napi::Number::New(env, 0));
|
|
764
|
+
result.Set("total", Napi::Number::New(env, 0));
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return result;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* createIterator(objectId, path[]) → Promise<{iteratorId}>
|
|
772
|
+
*/
|
|
773
|
+
static Napi::Value CreateIterator(const Napi::CallbackInfo& info) {
|
|
774
|
+
Napi::Env env = info.Env();
|
|
775
|
+
uint32_t objectId = info[0].As<Napi::Number>().Uint32Value();
|
|
776
|
+
|
|
777
|
+
std::vector<std::string> path;
|
|
778
|
+
if (info[1].IsArray()) {
|
|
779
|
+
Napi::Array pathArr = info[1].As<Napi::Array>();
|
|
780
|
+
for (uint32_t i = 0; i < pathArr.Length(); ++i) {
|
|
781
|
+
path.push_back(pathArr.Get(i).As<Napi::String>().Utf8Value());
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return SubmitTask(env, [objectId, path]() -> TaskResult {
|
|
786
|
+
return PythonBridge::getInstance().createIteratorSync(objectId, path);
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* iteratorNext(iteratorId) → Promise<{done, value?}>
|
|
792
|
+
*/
|
|
793
|
+
static Napi::Value IteratorNext(const Napi::CallbackInfo& info) {
|
|
794
|
+
Napi::Env env = info.Env();
|
|
795
|
+
uint32_t iteratorId = info[0].As<Napi::Number>().Uint32Value();
|
|
796
|
+
|
|
797
|
+
return SubmitTask(env, [iteratorId]() -> TaskResult {
|
|
798
|
+
return PythonBridge::getInstance().iteratorNextSync(iteratorId);
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* destroyIterator(iteratorId) → Promise<void>
|
|
804
|
+
*/
|
|
805
|
+
static Napi::Value DestroyIterator(const Napi::CallbackInfo& info) {
|
|
806
|
+
Napi::Env env = info.Env();
|
|
807
|
+
uint32_t iteratorId = info[0].As<Napi::Number>().Uint32Value();
|
|
808
|
+
|
|
809
|
+
return SubmitTask(env, [iteratorId]() -> TaskResult {
|
|
810
|
+
PythonBridge::getInstance().destroyIteratorSync(iteratorId);
|
|
811
|
+
TaskResult r;
|
|
812
|
+
r.success = true;
|
|
813
|
+
r.resultJson = "null";
|
|
814
|
+
return r;
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* resetProfilingStats() → void
|
|
820
|
+
*/
|
|
821
|
+
static Napi::Value ResetProfilingStats(const Napi::CallbackInfo& info) {
|
|
822
|
+
// Stats reset not implemented yet (would need atomic stores)
|
|
823
|
+
return info.Env().Undefined();
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ─── Module Registration ────────────────────────────────────────────────────
|
|
827
|
+
|
|
828
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
829
|
+
// Lifecycle
|
|
830
|
+
exports.Set("initializePython", Napi::Function::New(env, InitializePython));
|
|
831
|
+
exports.Set("finalizePython", Napi::Function::New(env, FinalizePython));
|
|
832
|
+
exports.Set("isInitialized", Napi::Function::New(env, IsInitialized));
|
|
833
|
+
|
|
834
|
+
// Module import
|
|
835
|
+
exports.Set("importModule", Napi::Function::New(env, ImportModule));
|
|
836
|
+
exports.Set("reloadModule", Napi::Function::New(env, ReloadModule));
|
|
837
|
+
|
|
838
|
+
// Attribute access
|
|
839
|
+
exports.Set("getAttribute", Napi::Function::New(env, GetAttribute));
|
|
840
|
+
exports.Set("getAttributePath", Napi::Function::New(env, GetAttributePath));
|
|
841
|
+
exports.Set("setAttribute", Napi::Function::New(env, SetAttribute));
|
|
842
|
+
exports.Set("hasAttribute", Napi::Function::New(env, HasAttribute));
|
|
843
|
+
exports.Set("deleteAttribute", Napi::Function::New(env, DeleteAttribute));
|
|
844
|
+
|
|
845
|
+
// Function calls
|
|
846
|
+
exports.Set("callFunction", Napi::Function::New(env, CallFunction));
|
|
847
|
+
exports.Set("callMethod", Napi::Function::New(env, CallMethod));
|
|
848
|
+
|
|
849
|
+
// Object inspection
|
|
850
|
+
exports.Set("getTypeInfo", Napi::Function::New(env, GetTypeInfo));
|
|
851
|
+
exports.Set("getObjectValue", Napi::Function::New(env, GetObjectValue));
|
|
852
|
+
exports.Set("getObjectRepr", Napi::Function::New(env, GetObjectRepr));
|
|
853
|
+
exports.Set("getObjectLength", Napi::Function::New(env, GetObjectLength));
|
|
854
|
+
exports.Set("getObjectKeys", Napi::Function::New(env, GetObjectKeys));
|
|
855
|
+
|
|
856
|
+
// Iterator support
|
|
857
|
+
exports.Set("createIterator", Napi::Function::New(env, CreateIterator));
|
|
858
|
+
exports.Set("iteratorNext", Napi::Function::New(env, IteratorNext));
|
|
859
|
+
exports.Set("destroyIterator", Napi::Function::New(env, DestroyIterator));
|
|
860
|
+
|
|
861
|
+
// Raw Python execution
|
|
862
|
+
exports.Set("runPythonCode", Napi::Function::New(env, RunPythonCode));
|
|
863
|
+
exports.Set("runPythonFile", Napi::Function::New(env, RunPythonFile));
|
|
864
|
+
exports.Set("evalPython", Napi::Function::New(env, EvalPython));
|
|
865
|
+
|
|
866
|
+
// Object lifecycle
|
|
867
|
+
exports.Set("incRef", Napi::Function::New(env, IncRef));
|
|
868
|
+
exports.Set("decRef", Napi::Function::New(env, DecRef));
|
|
869
|
+
exports.Set("collectGarbage", Napi::Function::New(env, CollectGarbage));
|
|
870
|
+
|
|
871
|
+
// Memory & diagnostics
|
|
872
|
+
exports.Set("getMemoryStats", Napi::Function::New(env, GetMemoryStats));
|
|
873
|
+
exports.Set("getProfilingReport", Napi::Function::New(env, GetProfilingReport));
|
|
874
|
+
exports.Set("resetProfilingStats", Napi::Function::New(env, ResetProfilingStats));
|
|
875
|
+
|
|
876
|
+
// Threading
|
|
877
|
+
exports.Set("setThreadPoolSize", Napi::Function::New(env, SetThreadPoolSize));
|
|
878
|
+
exports.Set("getThreadPoolStats", Napi::Function::New(env, GetThreadPoolStats));
|
|
879
|
+
|
|
880
|
+
return exports;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
} // namespace nodepyx
|
|
884
|
+
|
|
885
|
+
NODE_API_MODULE(nodepyx_addon, nodepyx::Init)
|
|
886
|
+
|