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,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nodepyx — TypeGenerator
|
|
3
|
+
* Generates TypeScript .d.ts stub files from Python module introspection.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. Check StubCache — return cached stub if fresh.
|
|
7
|
+
* 2. Run Python introspection via addon.runPythonCode()
|
|
8
|
+
* (executes type_inspector.py in the embedded Python runtime).
|
|
9
|
+
* 3. Parse the JSON introspection result.
|
|
10
|
+
* 4. Generate .d.ts content using PythonTypeMapper.
|
|
11
|
+
* 5. Store in StubCache.
|
|
12
|
+
* 6. Return type info array (PyTypeInfo[]).
|
|
13
|
+
*
|
|
14
|
+
* Generated stubs are compatible with TypeScript LSP — they provide
|
|
15
|
+
* real autocomplete inside editors without running Python.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { PythonTypeMapper, mapReturnType } from './PythonTypeMapper';
|
|
20
|
+
import { StubCache } from './StubCache';
|
|
21
|
+
import { Logger } from '../utils/Logger';
|
|
22
|
+
import type {
|
|
23
|
+
PyModuleInspection,
|
|
24
|
+
PyCallableInfo,
|
|
25
|
+
PyClassInfo,
|
|
26
|
+
PyMemberInfo,
|
|
27
|
+
PyParamInfo,
|
|
28
|
+
} from './python';
|
|
29
|
+
|
|
30
|
+
const logger = new Logger('TypeGenerator');
|
|
31
|
+
|
|
32
|
+
// ─── Re-export for convenience ────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export type { PyModuleInspection, PyCallableInfo, PyClassInfo, PyMemberInfo };
|
|
35
|
+
|
|
36
|
+
// ─── Options ─────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export interface TypeGeneratorOptions {
|
|
39
|
+
/** Directory to store generated .d.ts files. Default: './.nodepyx/stubs' */
|
|
40
|
+
stubsDir?: string;
|
|
41
|
+
/** nodepyx version string for cache validation */
|
|
42
|
+
nodepyxVersion?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Whether to emit strict types (never fall back to 'unknown').
|
|
45
|
+
* Default: false
|
|
46
|
+
*/
|
|
47
|
+
strictTypes?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Maximum number of members to include per class in the generated stub.
|
|
50
|
+
* Default: 200
|
|
51
|
+
*/
|
|
52
|
+
maxMembersPerClass?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Whether to include private members (names starting with _).
|
|
55
|
+
* Default: false
|
|
56
|
+
*/
|
|
57
|
+
includePrivate?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Whether to include inherited members.
|
|
60
|
+
* Default: false
|
|
61
|
+
*/
|
|
62
|
+
includeInherited?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Raw introspection JSON schema (from type_inspector.py) ───────────────
|
|
66
|
+
|
|
67
|
+
export interface RawParamInfo {
|
|
68
|
+
name: string;
|
|
69
|
+
type?: string;
|
|
70
|
+
annotation?: string;
|
|
71
|
+
optional?: boolean;
|
|
72
|
+
has_default?: boolean;
|
|
73
|
+
default?: string;
|
|
74
|
+
kind?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface RawCallableInfo {
|
|
78
|
+
name: string;
|
|
79
|
+
qual_name?: string;
|
|
80
|
+
docstring?: string;
|
|
81
|
+
parameters?: RawParamInfo[];
|
|
82
|
+
return_type?: string;
|
|
83
|
+
return_annotation?: string;
|
|
84
|
+
is_method?: boolean;
|
|
85
|
+
is_class_method?: boolean;
|
|
86
|
+
is_static_method?: boolean;
|
|
87
|
+
is_coroutine?: boolean;
|
|
88
|
+
is_generator?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface RawMemberInfo {
|
|
92
|
+
name: string;
|
|
93
|
+
type?: string;
|
|
94
|
+
read_only?: boolean;
|
|
95
|
+
docstring?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface RawClassInfo {
|
|
99
|
+
name: string;
|
|
100
|
+
qual_name?: string;
|
|
101
|
+
docstring?: string;
|
|
102
|
+
bases?: string[];
|
|
103
|
+
members?: RawMemberInfo[];
|
|
104
|
+
methods?: RawCallableInfo[];
|
|
105
|
+
properties?: RawMemberInfo[];
|
|
106
|
+
class_methods?: RawCallableInfo[];
|
|
107
|
+
static_methods?: RawCallableInfo[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface RawModuleInspection {
|
|
111
|
+
name: string;
|
|
112
|
+
file?: string;
|
|
113
|
+
docstring?: string;
|
|
114
|
+
version?: string;
|
|
115
|
+
functions?: RawCallableInfo[];
|
|
116
|
+
classes?: RawClassInfo[];
|
|
117
|
+
constants?: RawMemberInfo[];
|
|
118
|
+
submodules?: string[];
|
|
119
|
+
error?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* TypeGenerator — generates TypeScript type stubs from Python modules.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const gen = new TypeGenerator({ stubsDir: './.nodepyx/stubs' });
|
|
128
|
+
*
|
|
129
|
+
* // Generate stub (uses cache if available)
|
|
130
|
+
* const dts = await gen.generateStub('pandas');
|
|
131
|
+
*
|
|
132
|
+
* // Access parsed type info
|
|
133
|
+
* const info = await gen.inspectModule('pandas');
|
|
134
|
+
* console.log(info.classes.map(c => c.name));
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export class TypeGenerator {
|
|
138
|
+
private readonly _options: Required<TypeGeneratorOptions>;
|
|
139
|
+
private readonly _cache: StubCache;
|
|
140
|
+
|
|
141
|
+
constructor(options: TypeGeneratorOptions = {}) {
|
|
142
|
+
this._options = {
|
|
143
|
+
stubsDir: options.stubsDir ?? path.join(process.cwd(), '.nodepyx', 'stubs'),
|
|
144
|
+
nodepyxVersion: options.nodepyxVersion ?? '1.0.0',
|
|
145
|
+
strictTypes: options.strictTypes ?? false,
|
|
146
|
+
maxMembersPerClass: options.maxMembersPerClass ?? 200,
|
|
147
|
+
includePrivate: options.includePrivate ?? false,
|
|
148
|
+
includeInherited: options.includeInherited ?? false,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
this._cache = new StubCache({
|
|
152
|
+
stubsDir: this._options.stubsDir,
|
|
153
|
+
nodepyxVersion: this._options.nodepyxVersion,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Pre-populate cache with bundled stubs for common libraries
|
|
157
|
+
this._cache.initPrebuilts();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── Public API ──────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate (or return cached) TypeScript stub for a Python module.
|
|
164
|
+
* Returns the .d.ts content as a string.
|
|
165
|
+
*/
|
|
166
|
+
async generateStub(
|
|
167
|
+
moduleName: string,
|
|
168
|
+
inspection: RawModuleInspection,
|
|
169
|
+
libVersion?: string,
|
|
170
|
+
): Promise<string> {
|
|
171
|
+
// ── Cache check ──────────────────────────────────────────────────────
|
|
172
|
+
const cached = this._cache.get(moduleName, libVersion);
|
|
173
|
+
if (cached) {
|
|
174
|
+
logger.debug(`TypeGenerator: cache hit for '${moduleName}'`);
|
|
175
|
+
return cached;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
logger.info(`TypeGenerator: generating stub for '${moduleName}'`);
|
|
179
|
+
|
|
180
|
+
if (inspection.error) {
|
|
181
|
+
logger.warn(`TypeGenerator: introspection error for '${moduleName}': ${inspection.error}`);
|
|
182
|
+
const fallback = this._generateFallbackStub(moduleName, inspection.error);
|
|
183
|
+
this._cache.set(moduleName, fallback, { libVersion });
|
|
184
|
+
return fallback;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const dts = this._generateDTS(moduleName, inspection);
|
|
188
|
+
this._cache.set(moduleName, dts, { libVersion });
|
|
189
|
+
|
|
190
|
+
return dts;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Parse raw introspection JSON from Python into typed structures.
|
|
195
|
+
*/
|
|
196
|
+
parseInspection(raw: RawModuleInspection): PyModuleInspection {
|
|
197
|
+
return {
|
|
198
|
+
name: raw.name,
|
|
199
|
+
file: raw.file,
|
|
200
|
+
docstring: raw.docstring ?? '',
|
|
201
|
+
version: raw.version,
|
|
202
|
+
functions: (raw.functions ?? []).map(f => this._parseCallable(f)),
|
|
203
|
+
classes: (raw.classes ?? []).map(c => this._parseClass(c)),
|
|
204
|
+
constants: (raw.constants ?? []).map(m => this._parseMember(m)),
|
|
205
|
+
submodules: raw.submodules ?? [],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Return the StubCache instance.
|
|
211
|
+
*/
|
|
212
|
+
get cache(): StubCache {
|
|
213
|
+
return this._cache;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── DTS Generation ───────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
private _generateDTS(moduleName: string, raw: RawModuleInspection): string {
|
|
219
|
+
const mapper = new PythonTypeMapper({
|
|
220
|
+
moduleName,
|
|
221
|
+
strict: this._options.strictTypes,
|
|
222
|
+
knownClasses: new Set((raw.classes ?? []).map(c => c.name)),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const lines: string[] = [];
|
|
226
|
+
|
|
227
|
+
// ── File header ──────────────────────────────────────────────────────
|
|
228
|
+
lines.push(`// =============================================================`);
|
|
229
|
+
lines.push(`// nodepyx — Auto-generated TypeScript stub`);
|
|
230
|
+
lines.push(`// Module : ${moduleName}`);
|
|
231
|
+
if (raw.version) {lines.push(`// Version: ${raw.version}`);}
|
|
232
|
+
lines.push(`// Generated: ${new Date().toISOString()}`);
|
|
233
|
+
lines.push(`// DO NOT EDIT — regenerate with: npx nodepyx generate-stubs ${moduleName}`);
|
|
234
|
+
lines.push(`// =============================================================`);
|
|
235
|
+
lines.push(``);
|
|
236
|
+
|
|
237
|
+
// ── Shared type imports ───────────────────────────────────────────────
|
|
238
|
+
lines.push(`import type { PyProxy, DataFrameResult, SeriesResult } from 'nodepyx';`);
|
|
239
|
+
lines.push(``);
|
|
240
|
+
|
|
241
|
+
// ── Module declaration ────────────────────────────────────────────────
|
|
242
|
+
lines.push(`declare module 'nodepyx/${moduleName}' {`);
|
|
243
|
+
lines.push(``);
|
|
244
|
+
|
|
245
|
+
// Module docstring
|
|
246
|
+
if (raw.docstring) {
|
|
247
|
+
lines.push(...this._docComment(raw.docstring, 2));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
251
|
+
for (const constant of raw.constants ?? []) {
|
|
252
|
+
if (!this._options.includePrivate && constant.name.startsWith('_')) {continue;}
|
|
253
|
+
if (constant.docstring) {lines.push(...this._docComment(constant.docstring, 2));}
|
|
254
|
+
const tsType = mapper.map(constant.type ?? 'Any');
|
|
255
|
+
lines.push(` export const ${constant.name}: ${tsType};`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if ((raw.constants ?? []).length > 0) {lines.push(``);}
|
|
259
|
+
|
|
260
|
+
// ── Top-level functions ───────────────────────────────────────────────
|
|
261
|
+
for (const fn of raw.functions ?? []) {
|
|
262
|
+
if (!this._options.includePrivate && fn.name.startsWith('_')) {continue;}
|
|
263
|
+
lines.push(...this._functionDecl(fn, mapper, 2, 'export function'));
|
|
264
|
+
lines.push(``);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Classes ───────────────────────────────────────────────────────────
|
|
268
|
+
for (const cls of raw.classes ?? []) {
|
|
269
|
+
if (!this._options.includePrivate && cls.name.startsWith('_')) {continue;}
|
|
270
|
+
lines.push(...this._classDecl(cls, mapper));
|
|
271
|
+
lines.push(``);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── Sub-module re-exports ─────────────────────────────────────────────
|
|
275
|
+
for (const sub of raw.submodules ?? []) {
|
|
276
|
+
lines.push(` export const ${sub}: PyProxy;`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
lines.push(`}`);
|
|
280
|
+
lines.push(``);
|
|
281
|
+
|
|
282
|
+
return lines.join('\n');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private _functionDecl(
|
|
286
|
+
fn: RawCallableInfo,
|
|
287
|
+
mapper: PythonTypeMapper,
|
|
288
|
+
indent: number,
|
|
289
|
+
prefix: string,
|
|
290
|
+
): string[] {
|
|
291
|
+
const pad = ' '.repeat(indent);
|
|
292
|
+
const lines: string[] = [];
|
|
293
|
+
|
|
294
|
+
if (fn.docstring) {
|
|
295
|
+
lines.push(...this._docComment(fn.docstring, indent));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const params = (fn.parameters ?? [])
|
|
299
|
+
.filter(p => p.name !== 'self' && p.name !== 'cls')
|
|
300
|
+
.filter(p => this._options.includePrivate || !p.name.startsWith('_'))
|
|
301
|
+
.slice(0, 30) // sanity cap
|
|
302
|
+
.map(p => this._paramDecl(p, mapper));
|
|
303
|
+
|
|
304
|
+
const retType = mapReturnType(
|
|
305
|
+
fn.return_type ?? fn.return_annotation ?? 'None',
|
|
306
|
+
{ strict: this._options.strictTypes },
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
lines.push(`${pad}${prefix} ${fn.name}(${params.join(', ')}): ${retType};`);
|
|
310
|
+
|
|
311
|
+
return lines;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private _paramDecl(p: RawParamInfo, mapper: PythonTypeMapper): string {
|
|
315
|
+
const name = p.name;
|
|
316
|
+
const raw = p.type ?? p.annotation ?? 'Any';
|
|
317
|
+
const isOptional = p.optional ?? p.has_default ?? false;
|
|
318
|
+
const isVarArgs = p.kind === 'var_positional';
|
|
319
|
+
const isKwArgs = p.kind === 'var_keyword';
|
|
320
|
+
|
|
321
|
+
const tsType = mapper.map(raw);
|
|
322
|
+
|
|
323
|
+
if (isVarArgs) {
|
|
324
|
+
return `...${name}: Array<${tsType}>`;
|
|
325
|
+
}
|
|
326
|
+
if (isKwArgs) {
|
|
327
|
+
return `${name}?: Record<string, ${tsType}>`;
|
|
328
|
+
}
|
|
329
|
+
if (isOptional) {
|
|
330
|
+
return `${name}?: ${tsType} | null`;
|
|
331
|
+
}
|
|
332
|
+
return `${name}: ${tsType}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private _classDecl(cls: RawClassInfo, mapper: PythonTypeMapper): string[] {
|
|
336
|
+
const lines: string[] = [];
|
|
337
|
+
const pad = ' ';
|
|
338
|
+
|
|
339
|
+
if (cls.docstring) {
|
|
340
|
+
lines.push(...this._docComment(cls.docstring, 2));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const bases = (cls.bases ?? [])
|
|
344
|
+
.filter(b => b !== 'object')
|
|
345
|
+
.map(b => mapper.map(b))
|
|
346
|
+
.join(', ');
|
|
347
|
+
|
|
348
|
+
lines.push(`${pad}export class ${cls.name}${bases ? ` extends ${bases}` : ''} {`);
|
|
349
|
+
|
|
350
|
+
// Constructor
|
|
351
|
+
const initMethod = (cls.methods ?? []).find(m => m.name === '__init__');
|
|
352
|
+
if (initMethod) {
|
|
353
|
+
const ctorParams = (initMethod.parameters ?? [])
|
|
354
|
+
.filter(p => p.name !== 'self')
|
|
355
|
+
.map(p => this._paramDecl(p, mapper));
|
|
356
|
+
lines.push(`${pad} constructor(${ctorParams.join(', ')});`);
|
|
357
|
+
} else {
|
|
358
|
+
lines.push(`${pad} constructor(...args: unknown[]);`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Properties
|
|
362
|
+
const allProps = [
|
|
363
|
+
...(cls.properties ?? []),
|
|
364
|
+
...(cls.members ?? []).filter(m => !m.name.startsWith('__')),
|
|
365
|
+
].slice(0, this._options.maxMembersPerClass);
|
|
366
|
+
|
|
367
|
+
for (const prop of allProps) {
|
|
368
|
+
if (!this._options.includePrivate && prop.name.startsWith('_')) {continue;}
|
|
369
|
+
const tsType = mapper.map(prop.type ?? 'Any');
|
|
370
|
+
const ro = prop.read_only ? 'readonly ' : '';
|
|
371
|
+
lines.push(`${pad} ${ro}${prop.name}: Promise<${tsType}>;`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Methods
|
|
375
|
+
const allMethods = [
|
|
376
|
+
...(cls.methods ?? []).filter(m => m.name !== '__init__'),
|
|
377
|
+
...(cls.static_methods ?? []),
|
|
378
|
+
...(cls.class_methods ?? []),
|
|
379
|
+
].slice(0, this._options.maxMembersPerClass);
|
|
380
|
+
|
|
381
|
+
for (const method of allMethods) {
|
|
382
|
+
if (!this._options.includePrivate && method.name.startsWith('_')) {
|
|
383
|
+
if (!['__len__', '__str__', '__repr__', '__iter__'].includes(method.name)) {continue;}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (method.docstring) {
|
|
387
|
+
lines.push(...this._docComment(method.docstring, 4));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const isStatic = (cls.static_methods ?? []).some(s => s.name === method.name);
|
|
391
|
+
const prefix = isStatic ? 'static ' : '';
|
|
392
|
+
|
|
393
|
+
const params = (method.parameters ?? [])
|
|
394
|
+
.filter(p => p.name !== 'self' && p.name !== 'cls')
|
|
395
|
+
.slice(0, 20)
|
|
396
|
+
.map(p => this._paramDecl(p, mapper));
|
|
397
|
+
|
|
398
|
+
const retType = mapReturnType(
|
|
399
|
+
method.return_type ?? method.return_annotation ?? 'None',
|
|
400
|
+
{ strict: this._options.strictTypes },
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
lines.push(`${pad} ${prefix}${method.name}(${params.join(', ')}): ${retType};`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
lines.push(`${pad} [key: string]: unknown;`);
|
|
407
|
+
lines.push(`${pad}}`);
|
|
408
|
+
|
|
409
|
+
return lines;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private _generateFallbackStub(moduleName: string, error: string): string {
|
|
413
|
+
return [
|
|
414
|
+
`// nodepyx — Fallback stub for '${moduleName}'`,
|
|
415
|
+
`// Introspection error: ${error}`,
|
|
416
|
+
`// Install the module to get proper types.`,
|
|
417
|
+
``,
|
|
418
|
+
`declare module 'nodepyx/${moduleName}' {`,
|
|
419
|
+
` export const [key: string]: unknown;`,
|
|
420
|
+
`}`,
|
|
421
|
+
``,
|
|
422
|
+
].join('\n');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ─── Parsing helpers ──────────────────────────────────────────────────────
|
|
426
|
+
|
|
427
|
+
private _parseCallable(raw: RawCallableInfo): PyCallableInfo {
|
|
428
|
+
return {
|
|
429
|
+
name: raw.name,
|
|
430
|
+
qualName: raw.qual_name ?? raw.name,
|
|
431
|
+
docstring: raw.docstring ?? '',
|
|
432
|
+
parameters: (raw.parameters ?? []).map(p => this._parseParam(p)),
|
|
433
|
+
returnType: raw.return_type ?? raw.return_annotation ?? 'None',
|
|
434
|
+
isMethod: raw.is_method ?? false,
|
|
435
|
+
isClassMethod: raw.is_class_method ?? false,
|
|
436
|
+
isStaticMethod: raw.is_static_method ?? false,
|
|
437
|
+
isCoroutine: raw.is_coroutine ?? false,
|
|
438
|
+
isGenerator: raw.is_generator ?? false,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private _parseParam(raw: RawParamInfo): PyParamInfo {
|
|
443
|
+
return {
|
|
444
|
+
name: raw.name,
|
|
445
|
+
type: raw.type ?? raw.annotation ?? 'Any',
|
|
446
|
+
optional: raw.optional ?? raw.has_default ?? false,
|
|
447
|
+
hasDefault: raw.has_default ?? false,
|
|
448
|
+
defaultValue: raw.default,
|
|
449
|
+
kind: (raw.kind as PyParamInfo['kind']) ?? 'positional_or_keyword',
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private _parseClass(raw: RawClassInfo): PyClassInfo {
|
|
454
|
+
return {
|
|
455
|
+
name: raw.name,
|
|
456
|
+
qualName: raw.qual_name ?? raw.name,
|
|
457
|
+
docstring: raw.docstring ?? '',
|
|
458
|
+
bases: raw.bases ?? [],
|
|
459
|
+
members: (raw.members ?? []).map(m => this._parseMember(m)),
|
|
460
|
+
methods: (raw.methods ?? []).map(f => this._parseCallable(f)),
|
|
461
|
+
properties: (raw.properties ?? []).map(m => this._parseMember(m)),
|
|
462
|
+
classMethods: (raw.class_methods ?? []).map(f => this._parseCallable(f)),
|
|
463
|
+
staticMethods: (raw.static_methods ?? []).map(f => this._parseCallable(f)),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private _parseMember(raw: RawMemberInfo): PyMemberInfo {
|
|
468
|
+
return {
|
|
469
|
+
name: raw.name,
|
|
470
|
+
type: raw.type ?? 'Any',
|
|
471
|
+
isReadOnly: raw.read_only ?? false,
|
|
472
|
+
docstring: raw.docstring,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ─── JSDoc helpers ─────────────────────────────────────────────────────────
|
|
477
|
+
|
|
478
|
+
private _docComment(docstring: string, indent: number): string[] {
|
|
479
|
+
const pad = ' '.repeat(indent);
|
|
480
|
+
const lines = docstring.split('\n').slice(0, 8); // cap at 8 lines
|
|
481
|
+
if (lines.length === 1) {
|
|
482
|
+
return [`${pad}/** ${lines[0]!.trim()} */`];
|
|
483
|
+
}
|
|
484
|
+
return [
|
|
485
|
+
`${pad}/**`,
|
|
486
|
+
...lines.map(l => `${pad} * ${l.trim()}`),
|
|
487
|
+
`${pad} */`,
|
|
488
|
+
];
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nodepyx — Native Addon TypeScript Interface
|
|
3
|
+
* Type declarations for the C++ N-API addon.
|
|
4
|
+
* These map exactly to the exported functions in nodepyx_addon.cpp.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
SerializedValue,
|
|
9
|
+
PyCallResult,
|
|
10
|
+
PyIteratorNext,
|
|
11
|
+
PyTypeInfo,
|
|
12
|
+
AttributePath,
|
|
13
|
+
} from './python';
|
|
14
|
+
|
|
15
|
+
export interface AddonInitOptions {
|
|
16
|
+
/** Path to Python home directory */
|
|
17
|
+
pythonHome: string;
|
|
18
|
+
/** Path to Python executable */
|
|
19
|
+
pythonExecutable: string;
|
|
20
|
+
/** Thread pool size */
|
|
21
|
+
threadPoolSize: number;
|
|
22
|
+
/** Max thread queue size */
|
|
23
|
+
maxQueueSize: number;
|
|
24
|
+
/** GIL release interval (microseconds) */
|
|
25
|
+
gilReleaseInterval: number;
|
|
26
|
+
/** Call timeout (milliseconds) */
|
|
27
|
+
callTimeout: number;
|
|
28
|
+
/** Additional Python sys.path entries */
|
|
29
|
+
pythonPathExtra: string[];
|
|
30
|
+
/** Environment variables */
|
|
31
|
+
env: Record<string, string>;
|
|
32
|
+
/** Enable profiling */
|
|
33
|
+
enableProfiling: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AddonImportResult {
|
|
37
|
+
objectId: number;
|
|
38
|
+
success: boolean;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AddonAttributeResult {
|
|
43
|
+
success: boolean;
|
|
44
|
+
value?: SerializedValue;
|
|
45
|
+
objectId?: number;
|
|
46
|
+
isCallable: boolean;
|
|
47
|
+
isObject: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AddonCallOptions {
|
|
52
|
+
/** Positional arguments */
|
|
53
|
+
args: SerializedValue[];
|
|
54
|
+
/** Keyword arguments */
|
|
55
|
+
kwargs: Record<string, SerializedValue>;
|
|
56
|
+
/** Call timeout override (ms) */
|
|
57
|
+
timeout?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface AddonIteratorResult {
|
|
61
|
+
iteratorId: number;
|
|
62
|
+
success: boolean;
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface AddonMemoryStats {
|
|
67
|
+
pythonHeapBytes: number;
|
|
68
|
+
pythonHeapPeakBytes: number;
|
|
69
|
+
trackedObjects: number;
|
|
70
|
+
pendingCallbacks: number;
|
|
71
|
+
threadPoolActive: number;
|
|
72
|
+
threadPoolQueued: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface AddonProfilingReport {
|
|
76
|
+
totalCalls: number;
|
|
77
|
+
totalTimeMs: number;
|
|
78
|
+
avgCallTimeMs: number;
|
|
79
|
+
peakCallTimeMs: number;
|
|
80
|
+
callsByModule: Record<string, number>;
|
|
81
|
+
callsByFunction: Record<string, number>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The full interface of the compiled C++ addon module.
|
|
86
|
+
* All async methods return Promises (resolved on libuv callback).
|
|
87
|
+
*/
|
|
88
|
+
export interface NativeAddon {
|
|
89
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
90
|
+
initializePython(options: AddonInitOptions): Promise<void>;
|
|
91
|
+
finalizePython(): Promise<void>;
|
|
92
|
+
isInitialized(): boolean;
|
|
93
|
+
|
|
94
|
+
// ─── Module Import ─────────────────────────────────────────────────────────
|
|
95
|
+
importModule(moduleName: string): Promise<AddonImportResult>;
|
|
96
|
+
reloadModule(objectId: number): Promise<AddonImportResult>;
|
|
97
|
+
|
|
98
|
+
// ─── Attribute Access ──────────────────────────────────────────────────────
|
|
99
|
+
getAttribute(objectId: number, name: string): Promise<AddonAttributeResult>;
|
|
100
|
+
getAttributePath(objectId: number, path: AttributePath): Promise<AddonAttributeResult>;
|
|
101
|
+
setAttribute(objectId: number, name: string, value: SerializedValue): Promise<boolean>;
|
|
102
|
+
hasAttribute(objectId: number, name: string): Promise<boolean>;
|
|
103
|
+
deleteAttribute(objectId: number, name: string): Promise<boolean>;
|
|
104
|
+
|
|
105
|
+
// ─── Function Calls ────────────────────────────────────────────────────────
|
|
106
|
+
callFunction(
|
|
107
|
+
objectId: number,
|
|
108
|
+
path: AttributePath,
|
|
109
|
+
options: AddonCallOptions
|
|
110
|
+
): Promise<PyCallResult>;
|
|
111
|
+
callMethod(
|
|
112
|
+
objectId: number,
|
|
113
|
+
methodName: string,
|
|
114
|
+
options: AddonCallOptions
|
|
115
|
+
): Promise<PyCallResult>;
|
|
116
|
+
|
|
117
|
+
// ─── Object Inspection ─────────────────────────────────────────────────────
|
|
118
|
+
getTypeInfo(objectId: number): Promise<PyTypeInfo>;
|
|
119
|
+
getObjectValue(objectId: number): Promise<SerializedValue>;
|
|
120
|
+
getObjectRepr(objectId: number): Promise<string>;
|
|
121
|
+
getObjectLength(objectId: number): Promise<number>;
|
|
122
|
+
getObjectKeys(objectId: number): Promise<string[]>;
|
|
123
|
+
|
|
124
|
+
// ─── Iterator Support ──────────────────────────────────────────────────────
|
|
125
|
+
createIterator(objectId: number, path: AttributePath): Promise<AddonIteratorResult>;
|
|
126
|
+
iteratorNext(iteratorId: number): Promise<PyIteratorNext>;
|
|
127
|
+
destroyIterator(iteratorId: number): Promise<void>;
|
|
128
|
+
|
|
129
|
+
// ─── Raw Python Execution ──────────────────────────────────────────────────
|
|
130
|
+
runPythonCode(code: string, globals?: SerializedValue): Promise<SerializedValue>;
|
|
131
|
+
runPythonFile(filePath: string): Promise<SerializedValue>;
|
|
132
|
+
evalPython(expression: string): Promise<SerializedValue>;
|
|
133
|
+
|
|
134
|
+
// ─── Object Lifecycle ──────────────────────────────────────────────────────
|
|
135
|
+
incRef(objectId: number): void;
|
|
136
|
+
decRef(objectId: number): void;
|
|
137
|
+
collectGarbage(): Promise<void>;
|
|
138
|
+
|
|
139
|
+
// ─── Memory & Diagnostics ─────────────────────────────────────────────────
|
|
140
|
+
getMemoryStats(): AddonMemoryStats;
|
|
141
|
+
getProfilingReport(): AddonProfilingReport;
|
|
142
|
+
resetProfilingStats(): void;
|
|
143
|
+
|
|
144
|
+
// ─── Threading ─────────────────────────────────────────────────────────────
|
|
145
|
+
setThreadPoolSize(size: number): Promise<void>;
|
|
146
|
+
getThreadPoolStats(): { active: number; queued: number; total: number };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Loads the native addon, trying prebuilds first then build directory.
|
|
151
|
+
*/
|
|
152
|
+
export function loadNativeAddon(): NativeAddon {
|
|
153
|
+
// Try prebuilt binary first (faster, no build tools needed)
|
|
154
|
+
try {
|
|
155
|
+
// node-gyp-build handles platform/arch detection automatically
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
157
|
+
const build = require('node-gyp-build');
|
|
158
|
+
return build(__dirname + '/../../') as NativeAddon;
|
|
159
|
+
} catch {
|
|
160
|
+
// Fall back to explicit build path
|
|
161
|
+
try {
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
163
|
+
return require('../../build/Release/nodepyx_addon.node') as NativeAddon;
|
|
164
|
+
} catch {
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
166
|
+
return require('../../build/Debug/nodepyx_addon.node') as NativeAddon;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|