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,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
|
+
|