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.
Files changed (184) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +399 -0
  3. package/binding.gyp +73 -0
  4. package/dist/core/PyCallable.d.ts +65 -0
  5. package/dist/core/PyCallable.d.ts.map +1 -0
  6. package/dist/core/PyCallable.js +109 -0
  7. package/dist/core/PyCallable.js.map +1 -0
  8. package/dist/core/PyContext.d.ts +76 -0
  9. package/dist/core/PyContext.d.ts.map +1 -0
  10. package/dist/core/PyContext.js +228 -0
  11. package/dist/core/PyContext.js.map +1 -0
  12. package/dist/core/PyIterator.d.ts +84 -0
  13. package/dist/core/PyIterator.d.ts.map +1 -0
  14. package/dist/core/PyIterator.js +243 -0
  15. package/dist/core/PyIterator.js.map +1 -0
  16. package/dist/core/PyModule.d.ts +55 -0
  17. package/dist/core/PyModule.d.ts.map +1 -0
  18. package/dist/core/PyModule.js +172 -0
  19. package/dist/core/PyModule.js.map +1 -0
  20. package/dist/core/PyProxy.d.ts +65 -0
  21. package/dist/core/PyProxy.d.ts.map +1 -0
  22. package/dist/core/PyProxy.js +483 -0
  23. package/dist/core/PyProxy.js.map +1 -0
  24. package/dist/core/PyRuntime.d.ts +105 -0
  25. package/dist/core/PyRuntime.d.ts.map +1 -0
  26. package/dist/core/PyRuntime.js +438 -0
  27. package/dist/core/PyRuntime.js.map +1 -0
  28. package/dist/env/CondaManager.d.ts +118 -0
  29. package/dist/env/CondaManager.d.ts.map +1 -0
  30. package/dist/env/CondaManager.js +401 -0
  31. package/dist/env/CondaManager.js.map +1 -0
  32. package/dist/env/PackageInstaller.d.ts +233 -0
  33. package/dist/env/PackageInstaller.d.ts.map +1 -0
  34. package/dist/env/PackageInstaller.js +609 -0
  35. package/dist/env/PackageInstaller.js.map +1 -0
  36. package/dist/env/PythonDetector.d.ts +103 -0
  37. package/dist/env/PythonDetector.d.ts.map +1 -0
  38. package/dist/env/PythonDetector.js +381 -0
  39. package/dist/env/PythonDetector.js.map +1 -0
  40. package/dist/env/VenvManager.d.ts +117 -0
  41. package/dist/env/VenvManager.d.ts.map +1 -0
  42. package/dist/env/VenvManager.js +331 -0
  43. package/dist/env/VenvManager.js.map +1 -0
  44. package/dist/index.d.ts +169 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +393 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/plugins/Plugin.interface.d.ts +41 -0
  49. package/dist/plugins/Plugin.interface.d.ts.map +1 -0
  50. package/dist/plugins/Plugin.interface.js +12 -0
  51. package/dist/plugins/Plugin.interface.js.map +1 -0
  52. package/dist/plugins/PluginManager.d.ts +26 -0
  53. package/dist/plugins/PluginManager.d.ts.map +1 -0
  54. package/dist/plugins/PluginManager.js +174 -0
  55. package/dist/plugins/PluginManager.js.map +1 -0
  56. package/dist/plugins/builtin/NumpyPlugin.d.ts +17 -0
  57. package/dist/plugins/builtin/NumpyPlugin.d.ts.map +1 -0
  58. package/dist/plugins/builtin/NumpyPlugin.js +41 -0
  59. package/dist/plugins/builtin/NumpyPlugin.js.map +1 -0
  60. package/dist/plugins/builtin/PandasPlugin.d.ts +19 -0
  61. package/dist/plugins/builtin/PandasPlugin.d.ts.map +1 -0
  62. package/dist/plugins/builtin/PandasPlugin.js +57 -0
  63. package/dist/plugins/builtin/PandasPlugin.js.map +1 -0
  64. package/dist/plugins/builtin/TorchPlugin.d.ts +23 -0
  65. package/dist/plugins/builtin/TorchPlugin.d.ts.map +1 -0
  66. package/dist/plugins/builtin/TorchPlugin.js +50 -0
  67. package/dist/plugins/builtin/TorchPlugin.js.map +1 -0
  68. package/dist/plugins/index.d.ts +7 -0
  69. package/dist/plugins/index.d.ts.map +1 -0
  70. package/dist/plugins/index.js +12 -0
  71. package/dist/plugins/index.js.map +1 -0
  72. package/dist/serialization/DataFrameBridge.d.ts +141 -0
  73. package/dist/serialization/DataFrameBridge.d.ts.map +1 -0
  74. package/dist/serialization/DataFrameBridge.js +355 -0
  75. package/dist/serialization/DataFrameBridge.js.map +1 -0
  76. package/dist/serialization/MsgPackSerializer.d.ts +45 -0
  77. package/dist/serialization/MsgPackSerializer.d.ts.map +1 -0
  78. package/dist/serialization/MsgPackSerializer.js +242 -0
  79. package/dist/serialization/MsgPackSerializer.js.map +1 -0
  80. package/dist/serialization/NumpyBridge.d.ts +96 -0
  81. package/dist/serialization/NumpyBridge.d.ts.map +1 -0
  82. package/dist/serialization/NumpyBridge.js +323 -0
  83. package/dist/serialization/NumpyBridge.js.map +1 -0
  84. package/dist/serialization/Serializer.d.ts +78 -0
  85. package/dist/serialization/Serializer.d.ts.map +1 -0
  86. package/dist/serialization/Serializer.js +281 -0
  87. package/dist/serialization/Serializer.js.map +1 -0
  88. package/dist/types/PythonTypeMapper.d.ts +87 -0
  89. package/dist/types/PythonTypeMapper.d.ts.map +1 -0
  90. package/dist/types/PythonTypeMapper.js +449 -0
  91. package/dist/types/PythonTypeMapper.js.map +1 -0
  92. package/dist/types/StubCache.d.ts +109 -0
  93. package/dist/types/StubCache.d.ts.map +1 -0
  94. package/dist/types/StubCache.js +333 -0
  95. package/dist/types/StubCache.js.map +1 -0
  96. package/dist/types/TypeGenerator.d.ts +139 -0
  97. package/dist/types/TypeGenerator.d.ts.map +1 -0
  98. package/dist/types/TypeGenerator.js +372 -0
  99. package/dist/types/TypeGenerator.js.map +1 -0
  100. package/dist/types/addon.d.ts +114 -0
  101. package/dist/types/addon.d.ts.map +1 -0
  102. package/dist/types/addon.js +32 -0
  103. package/dist/types/addon.js.map +1 -0
  104. package/dist/types/config.d.ts +175 -0
  105. package/dist/types/config.d.ts.map +1 -0
  106. package/dist/types/config.js +35 -0
  107. package/dist/types/config.js.map +1 -0
  108. package/dist/types/index.d.ts +10 -0
  109. package/dist/types/index.d.ts.map +1 -0
  110. package/dist/types/index.js +12 -0
  111. package/dist/types/index.js.map +1 -0
  112. package/dist/types/python.d.ts +235 -0
  113. package/dist/types/python.d.ts.map +1 -0
  114. package/dist/types/python.js +7 -0
  115. package/dist/types/python.js.map +1 -0
  116. package/dist/utils/ErrorTranslator.d.ts +83 -0
  117. package/dist/utils/ErrorTranslator.d.ts.map +1 -0
  118. package/dist/utils/ErrorTranslator.js +210 -0
  119. package/dist/utils/ErrorTranslator.js.map +1 -0
  120. package/dist/utils/Logger.d.ts +27 -0
  121. package/dist/utils/Logger.d.ts.map +1 -0
  122. package/dist/utils/Logger.js +115 -0
  123. package/dist/utils/Logger.js.map +1 -0
  124. package/dist/utils/MemoryMonitor.d.ts +44 -0
  125. package/dist/utils/MemoryMonitor.d.ts.map +1 -0
  126. package/dist/utils/MemoryMonitor.js +143 -0
  127. package/dist/utils/MemoryMonitor.js.map +1 -0
  128. package/package.json +177 -0
  129. package/python/error_handler.py +433 -0
  130. package/python/nodepyx_runtime.py +575 -0
  131. package/python/serializer.py +379 -0
  132. package/python/type_inspector.py +288 -0
  133. package/scripts/build-native.js +68 -0
  134. package/scripts/download-prebuilds.js +99 -0
  135. package/scripts/generate-stubs.js +405 -0
  136. package/scripts/install.js +260 -0
  137. package/src/core/PyCallable.ts +137 -0
  138. package/src/core/PyContext.ts +296 -0
  139. package/src/core/PyIterator.ts +294 -0
  140. package/src/core/PyModule.ts +194 -0
  141. package/src/core/PyProxy.ts +605 -0
  142. package/src/core/PyRuntime.ts +504 -0
  143. package/src/env/CondaManager.ts +451 -0
  144. package/src/env/PackageInstaller.ts +738 -0
  145. package/src/env/PythonDetector.ts +414 -0
  146. package/src/env/VenvManager.ts +396 -0
  147. package/src/index.ts +425 -0
  148. package/src/native/gil_guard.cpp +26 -0
  149. package/src/native/gil_guard.h +175 -0
  150. package/src/native/nodepyx_addon.cpp +886 -0
  151. package/src/native/python_bridge.cpp +790 -0
  152. package/src/native/python_bridge.h +257 -0
  153. package/src/native/thread_pool.cpp +336 -0
  154. package/src/native/thread_pool.h +175 -0
  155. package/src/native/type_converter.cpp +901 -0
  156. package/src/native/type_converter.h +272 -0
  157. package/src/nextjs/PyProvider.tsx +123 -0
  158. package/src/nextjs/index.ts +21 -0
  159. package/src/nextjs/usePython.ts +106 -0
  160. package/src/nextjs/withnodepyx.ts +88 -0
  161. package/src/plugins/Plugin.interface.ts +51 -0
  162. package/src/plugins/PluginManager.ts +155 -0
  163. package/src/plugins/builtin/NumpyPlugin.ts +36 -0
  164. package/src/plugins/builtin/PandasPlugin.ts +49 -0
  165. package/src/plugins/builtin/TorchPlugin.ts +56 -0
  166. package/src/plugins/index.ts +7 -0
  167. package/src/serialization/DataFrameBridge.ts +398 -0
  168. package/src/serialization/MsgPackSerializer.ts +220 -0
  169. package/src/serialization/NumpyBridge.ts +332 -0
  170. package/src/serialization/Serializer.ts +320 -0
  171. package/src/types/PythonTypeMapper.ts +495 -0
  172. package/src/types/StubCache.ts +340 -0
  173. package/src/types/TypeGenerator.ts +491 -0
  174. package/src/types/addon.ts +170 -0
  175. package/src/types/config.ts +226 -0
  176. package/src/types/index.ts +55 -0
  177. package/src/types/python.ts +309 -0
  178. package/src/types/stubs/numpy.d.ts +441 -0
  179. package/src/types/stubs/pandas.d.ts +575 -0
  180. package/src/types/stubs/sklearn.d.ts +728 -0
  181. package/src/types/stubs/torch.d.ts +694 -0
  182. package/src/utils/ErrorTranslator.ts +220 -0
  183. package/src/utils/Logger.ts +119 -0
  184. 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
+