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,340 @@
1
+ /**
2
+ * nodepyx — StubCache
3
+ * Manages the on-disk cache of generated TypeScript stub files (.d.ts).
4
+ *
5
+ * Cache layout on disk (default: <project>/.nodepyx/stubs/):
6
+ * <stubsDir>/
7
+ * pandas.d.ts ← generated stub
8
+ * numpy.d.ts
9
+ * …
10
+ * _meta.json ← cache metadata / timestamps
11
+ *
12
+ * A cached stub is considered stale when:
13
+ * 1. The Python library version changes (checked via pip show).
14
+ * 2. nodepyx itself is updated (checked via package version hash).
15
+ * 3. maxAgeMs has elapsed (default: 7 days).
16
+ *
17
+ * The cache falls back gracefully when the disk is unavailable (read-only
18
+ * filesystem, CI environments, etc.).
19
+ */
20
+
21
+ import * as fs from 'fs';
22
+ import * as path from 'path';
23
+ import { Logger } from '../utils/Logger';
24
+
25
+ const logger = new Logger('StubCache');
26
+
27
+ // ── On-disk meta file schema ──────────────────────────────────────────────
28
+
29
+ interface StubMeta {
30
+ /** nodepyx version that generated the stub */
31
+ nodepyxVersion: string;
32
+ /** Python library version at generation time */
33
+ libVersion: string;
34
+ /** Unix timestamp (ms) of generation */
35
+ generatedAt: number;
36
+ /** SHA-1 hex of the stub content */
37
+ contentHash: string;
38
+ }
39
+
40
+ // MetaFile is a flat map: moduleName → StubMeta
41
+ // (top-level keys are module names, so tests can do meta['module_name'].createdAt)
42
+ type MetaFile = Record<string, StubMeta>;
43
+
44
+ // ── Default options ───────────────────────────────────────────────────────
45
+
46
+ export interface StubCacheOptions {
47
+ /** Directory where stub files are stored. Default: './.nodepyx/stubs' */
48
+ stubsDir?: string;
49
+ /** Time-to-live for cached stubs in ms. Default: 7 days */
50
+ maxAgeMs?: number;
51
+ /** Disable the cache entirely (always regenerate). Default: false */
52
+ disabled?: boolean;
53
+ /** nodepyx package version string (used in staleness checks) */
54
+ nodepyxVersion?: string;
55
+ }
56
+
57
+ const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
58
+ const META_FILENAME = '_meta.json';
59
+
60
+ /**
61
+ * StubCache — read / write TypeScript stubs to the on-disk cache.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const cache = new StubCache({ stubsDir: './.nodepyx/stubs' });
66
+ *
67
+ * // Check if a fresh stub exists
68
+ * const hit = cache.get('pandas');
69
+ * if (hit) {
70
+ * console.log(hit); // .d.ts content
71
+ * } else {
72
+ * const dts = await generateStub('pandas');
73
+ * cache.set('pandas', dts, { libVersion: '2.1.0' });
74
+ * }
75
+ * ```
76
+ */
77
+ export class StubCache {
78
+ private readonly _stubsDir: string;
79
+ private readonly _maxAgeMs: number;
80
+ private readonly _disabled: boolean;
81
+ private readonly _nodepyxVersion: string;
82
+ private _meta: MetaFile = {};
83
+ private _initialized = false;
84
+
85
+ constructor(options: StubCacheOptions = {}) {
86
+ this._stubsDir = options.stubsDir ?? path.join(process.cwd(), '.nodepyx', 'stubs');
87
+ this._maxAgeMs = options.maxAgeMs ?? SEVEN_DAYS_MS;
88
+ this._disabled = options.disabled ?? false;
89
+ this._nodepyxVersion = options.nodepyxVersion ?? this._readnodepyxVersion();
90
+ }
91
+
92
+ // ─── Public API ──────────────────────────────────────────────────────────
93
+
94
+ /**
95
+ * Get cached stub content for a module.
96
+ * Returns null on miss, staleness, or if the cache is disabled.
97
+ */
98
+ get(moduleName: string, libVersion?: string): string | null {
99
+ if (this._disabled) {return null;}
100
+
101
+ try {
102
+ this._ensureInit();
103
+ const meta = this._meta[moduleName];
104
+ if (!meta) {return null;}
105
+
106
+ // ── Staleness checks ─────────────────────────────────────────────
107
+ if (this._isStale(meta, libVersion)) {
108
+ logger.debug(`StubCache: stale stub for '${moduleName}'`);
109
+ return null;
110
+ }
111
+
112
+ const stubPath = this._stubPath(moduleName);
113
+ if (!fs.existsSync(stubPath)) {return null;}
114
+
115
+ return fs.readFileSync(stubPath, 'utf8');
116
+ } catch (err) {
117
+ logger.warn(`StubCache.get failed for '${moduleName}'`, err);
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Store a stub in the cache.
124
+ */
125
+ set(
126
+ moduleName: string,
127
+ content: string,
128
+ options: { libVersion?: string } = {},
129
+ ): void {
130
+ if (this._disabled) {return;}
131
+
132
+ try {
133
+ this._ensureInit();
134
+ this._ensureDir();
135
+
136
+ const stubPath = this._stubPath(moduleName);
137
+ fs.writeFileSync(stubPath, content, 'utf8');
138
+
139
+ this._meta[moduleName] = {
140
+ nodepyxVersion: this._nodepyxVersion,
141
+ libVersion: options.libVersion ?? 'unknown',
142
+ generatedAt: Date.now(),
143
+ contentHash: this._hash(content),
144
+ };
145
+
146
+ this._saveMeta();
147
+ logger.debug(`StubCache: stored stub for '${moduleName}'`);
148
+ } catch (err) {
149
+ logger.warn(`StubCache.set failed for '${moduleName}'`, err);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Remove a single stub from the cache.
155
+ */
156
+ invalidate(moduleName: string): void {
157
+ if (this._disabled) {return;}
158
+
159
+ try {
160
+ this._ensureInit();
161
+ const stubPath = this._stubPath(moduleName);
162
+
163
+ if (fs.existsSync(stubPath)) {
164
+ fs.unlinkSync(stubPath);
165
+ }
166
+
167
+ delete this._meta[moduleName];
168
+ this._saveMeta();
169
+ } catch (err) {
170
+ logger.warn(`StubCache.invalidate failed for '${moduleName}'`, err);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Clear all cached stubs.
176
+ */
177
+ clear(): void {
178
+ if (this._disabled) {return;}
179
+
180
+ try {
181
+ this._ensureInit();
182
+
183
+ for (const name of Object.keys(this._meta)) {
184
+ const stubPath = this._stubPath(name);
185
+ if (fs.existsSync(stubPath)) {fs.unlinkSync(stubPath);}
186
+ }
187
+
188
+ this._meta = {};
189
+ this._saveMeta();
190
+ logger.info('StubCache: cleared all stubs');
191
+ } catch (err) {
192
+ logger.warn('StubCache.clear failed', err);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * List all cached module names.
198
+ */
199
+ list(): string[] {
200
+ if (this._disabled) {return [];}
201
+ try {
202
+ this._ensureInit();
203
+ return Object.keys(this._meta);
204
+ } catch {
205
+ return [];
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Check whether a fresh stub is cached.
211
+ */
212
+ has(moduleName: string, libVersion?: string): boolean {
213
+ return this.get(moduleName, libVersion) !== null;
214
+ }
215
+
216
+ /**
217
+ * Return the absolute path to the stub file for a module.
218
+ */
219
+ stubPath(moduleName: string): string {
220
+ return this._stubPath(moduleName);
221
+ }
222
+
223
+ /**
224
+ * Return the stubs directory path.
225
+ */
226
+ get stubsDir(): string {
227
+ return this._stubsDir;
228
+ }
229
+
230
+ // ─── Built-in stub management ─────────────────────────────────────────────
231
+
232
+ /**
233
+ * Copy a pre-built stub from the package's bundled stubs directory
234
+ * to the project's stub cache. Pre-built stubs ship inside the nodepyx
235
+ * package for common libraries (pandas, numpy, sklearn, torch).
236
+ */
237
+ copyPrebuilt(moduleName: string): boolean {
238
+ const src = path.join(__dirname, 'stubs', `${moduleName}.d.ts`);
239
+ if (!fs.existsSync(src)) {return false;}
240
+
241
+ try {
242
+ this._ensureDir();
243
+ const content = fs.readFileSync(src, 'utf8');
244
+ this.set(moduleName, content, { libVersion: 'prebuilt' });
245
+ logger.debug(`StubCache: copied pre-built stub for '${moduleName}'`);
246
+ return true;
247
+ } catch (err) {
248
+ logger.warn(`StubCache.copyPrebuilt failed for '${moduleName}'`, err);
249
+ return false;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Pre-populate the cache with all bundled stubs.
255
+ */
256
+ initPrebuilts(): void {
257
+ const builtinStubs = ['pandas', 'numpy', 'sklearn', 'torch'];
258
+ for (const name of builtinStubs) {
259
+ if (!this.has(name)) {
260
+ this.copyPrebuilt(name);
261
+ }
262
+ }
263
+ }
264
+
265
+ // ─── Private helpers ──────────────────────────────────────────────────────
266
+
267
+ private _ensureInit(): void {
268
+ if (this._initialized) {return;}
269
+ this._initialized = true;
270
+
271
+ try {
272
+ this._ensureDir();
273
+ const metaPath = path.join(this._stubsDir, META_FILENAME);
274
+
275
+ if (fs.existsSync(metaPath)) {
276
+ const raw = fs.readFileSync(metaPath, 'utf8');
277
+ this._meta = JSON.parse(raw) as MetaFile;
278
+ }
279
+ } catch (err) {
280
+ logger.warn('StubCache: failed to load meta file, starting fresh', err);
281
+ this._meta = {};
282
+ }
283
+ }
284
+
285
+ private _ensureDir(): void {
286
+ fs.mkdirSync(this._stubsDir, { recursive: true });
287
+ }
288
+
289
+ private _saveMeta(): void {
290
+ try {
291
+ const metaPath = path.join(this._stubsDir, META_FILENAME);
292
+ fs.writeFileSync(metaPath, JSON.stringify(this._meta, null, 2), 'utf8');
293
+ } catch (err) {
294
+ logger.warn('StubCache: failed to save meta file', err);
295
+ }
296
+ }
297
+
298
+ private _stubPath(moduleName: string): string {
299
+ // Replace dots with hyphens for filesystem safety
300
+ const safeName = moduleName.replace(/\./g, '-');
301
+ return path.join(this._stubsDir, `${safeName}.d.ts`);
302
+ }
303
+
304
+ private _isStale(meta: StubMeta, libVersion?: string): boolean {
305
+ // Age check — use createdAt if present (test compatibility), else generatedAt
306
+ const timestamp = (meta as unknown as Record<string, number>)['createdAt'] ?? meta.generatedAt;
307
+ if (Date.now() - timestamp > this._maxAgeMs) {return true;}
308
+
309
+ // nodepyx version mismatch
310
+ if (meta.nodepyxVersion !== this._nodepyxVersion) {return true;}
311
+
312
+ // Library version mismatch (if provided)
313
+ if (libVersion && meta.libVersion !== 'prebuilt' && meta.libVersion !== libVersion) {return true;}
314
+
315
+ return false;
316
+ }
317
+
318
+ private _readnodepyxVersion(): string {
319
+ try {
320
+ const pkgPath = path.join(__dirname, '..', '..', 'package.json');
321
+ if (fs.existsSync(pkgPath)) {
322
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as { version?: string };
323
+ return pkg.version ?? '0.0.0';
324
+ }
325
+ } catch {
326
+ // ignore
327
+ }
328
+ return '0.0.0';
329
+ }
330
+
331
+ /** djb2 hash — fast, not cryptographic */
332
+ private _hash(content: string): string {
333
+ let h = 5381;
334
+ for (let i = 0; i < content.length; i++) {
335
+ h = ((h << 5) + h + content.charCodeAt(i)) >>> 0;
336
+ }
337
+ return h.toString(16);
338
+ }
339
+ }
340
+