@scelar/nodepod 1.0.3 → 1.0.5
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/dist/__sw__.js +642 -642
- package/dist/{child_process-hmVqFcF7.cjs → child_process-B9qsOKHs.cjs} +7434 -7434
- package/dist/child_process-B9qsOKHs.cjs.map +1 -0
- package/dist/{child_process-D6oDN2MX.js → child_process-PY34i_6n.js} +8233 -8233
- package/dist/child_process-PY34i_6n.js.map +1 -0
- package/dist/{index-BO1i013L.cjs → index-CyhVjVJU.cjs} +38383 -37240
- package/dist/index-CyhVjVJU.cjs.map +1 -0
- package/dist/index-D8Hn2kWU.js +36455 -0
- package/dist/index-D8Hn2kWU.js.map +1 -0
- package/dist/index.cjs +67 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +88 -86
- package/dist/index.mjs +61 -59
- package/dist/memory-handler.d.ts +57 -0
- package/dist/memory-volume.d.ts +157 -147
- package/dist/packages/installer.d.ts +44 -41
- package/dist/persistence/idb-cache.d.ts +7 -0
- package/dist/polyfills/wasi.d.ts +45 -4
- package/dist/script-engine.d.ts +84 -81
- package/dist/sdk/nodepod-process.d.ts +29 -28
- package/dist/sdk/nodepod.d.ts +59 -39
- package/dist/sdk/types.d.ts +64 -53
- package/dist/threading/process-manager.d.ts +1 -1
- package/dist/threading/worker-protocol.d.ts +1 -1
- package/package.json +1 -1
- package/src/index.ts +194 -192
- package/src/memory-handler.ts +168 -0
- package/src/memory-volume.ts +78 -8
- package/src/packages/installer.ts +49 -1
- package/src/packages/version-resolver.ts +421 -411
- package/src/persistence/idb-cache.ts +107 -0
- package/src/polyfills/child_process.ts +2288 -2288
- package/src/polyfills/events.ts +6 -2
- package/src/polyfills/fs.ts +2888 -2888
- package/src/polyfills/http.ts +1450 -1449
- package/src/polyfills/process.ts +27 -1
- package/src/polyfills/stream.ts +1621 -1620
- package/src/polyfills/wasi.ts +1306 -44
- package/src/polyfills/zlib.ts +881 -881
- package/src/request-proxy.ts +716 -716
- package/src/script-engine.ts +444 -118
- package/src/sdk/nodepod-process.ts +94 -86
- package/src/sdk/nodepod.ts +571 -509
- package/src/sdk/types.ts +82 -70
- package/src/syntax-transforms.ts +2 -2
- package/src/threading/offload-worker.ts +383 -383
- package/src/threading/offload.ts +271 -271
- package/src/threading/process-manager.ts +967 -956
- package/src/threading/process-worker-entry.ts +858 -854
- package/src/threading/worker-protocol.ts +358 -358
- package/dist/child_process-D6oDN2MX.js.map +0 -1
- package/dist/child_process-hmVqFcF7.cjs.map +0 -1
- package/dist/index-Ale2oba_.js +0 -35412
- package/dist/index-Ale2oba_.js.map +0 -1
- package/dist/index-BO1i013L.cjs.map +0 -1
package/src/memory-volume.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { VolumeSnapshot, VolumeEntry } from './engine-types';
|
|
4
4
|
import { bytesToBase64, base64ToBytes } from './helpers/byte-encoding';
|
|
5
5
|
import { MOCK_IDS, MOCK_FS } from './constants/config';
|
|
6
|
+
import type { MemoryHandler } from './memory-handler';
|
|
6
7
|
|
|
7
8
|
export interface VolumeNode {
|
|
8
9
|
kind: 'file' | 'directory' | 'symlink';
|
|
@@ -172,8 +173,10 @@ export class MemoryVolume {
|
|
|
172
173
|
private textDecoder = new TextDecoder();
|
|
173
174
|
private activeWatchers = new Map<string, Set<ActiveWatcher>>();
|
|
174
175
|
private subscribers = new Map<string, Set<VolumeEventHandler>>();
|
|
176
|
+
private _handler: MemoryHandler | null;
|
|
175
177
|
|
|
176
|
-
constructor() {
|
|
178
|
+
constructor(handler?: MemoryHandler | null) {
|
|
179
|
+
this._handler = handler ?? null;
|
|
177
180
|
this.tree = {
|
|
178
181
|
kind: 'directory',
|
|
179
182
|
children: new Map(),
|
|
@@ -216,15 +219,61 @@ export class MemoryVolume {
|
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
|
|
222
|
+
// ---- Stats ----
|
|
223
|
+
|
|
224
|
+
getStats(): { fileCount: number; totalBytes: number; dirCount: number; watcherCount: number } {
|
|
225
|
+
let fileCount = 0;
|
|
226
|
+
let totalBytes = 0;
|
|
227
|
+
let dirCount = 0;
|
|
228
|
+
const walk = (node: VolumeNode) => {
|
|
229
|
+
if (node.kind === 'file') {
|
|
230
|
+
fileCount++;
|
|
231
|
+
totalBytes += node.content?.byteLength ?? 0;
|
|
232
|
+
} else if (node.kind === 'directory') {
|
|
233
|
+
dirCount++;
|
|
234
|
+
if (node.children) {
|
|
235
|
+
for (const child of node.children.values()) walk(child);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
walk(this.tree);
|
|
240
|
+
let watcherCount = 0;
|
|
241
|
+
for (const set of this.activeWatchers.values()) watcherCount += set.size;
|
|
242
|
+
return { fileCount, totalBytes, dirCount, watcherCount };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Clean up all watchers, subscribers, and global listeners. */
|
|
246
|
+
dispose(): void {
|
|
247
|
+
this.activeWatchers.clear();
|
|
248
|
+
this.subscribers.clear();
|
|
249
|
+
this.globalChangeListeners.clear();
|
|
250
|
+
if (this._handler) {
|
|
251
|
+
this._handler.statCache.clear();
|
|
252
|
+
this._handler.pathNormCache.clear();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
219
256
|
// ---- Snapshot serialization ----
|
|
220
257
|
|
|
221
|
-
toSnapshot(): VolumeSnapshot {
|
|
258
|
+
toSnapshot(excludePrefixes?: string[], excludeDirNames?: Set<string>): VolumeSnapshot {
|
|
222
259
|
const entries: VolumeEntry[] = [];
|
|
223
|
-
this.collectEntries('/', this.tree, entries);
|
|
260
|
+
this.collectEntries('/', this.tree, entries, excludePrefixes, excludeDirNames);
|
|
224
261
|
return { entries };
|
|
225
262
|
}
|
|
226
263
|
|
|
227
|
-
private collectEntries(
|
|
264
|
+
private collectEntries(
|
|
265
|
+
currentPath: string,
|
|
266
|
+
node: VolumeNode,
|
|
267
|
+
result: VolumeEntry[],
|
|
268
|
+
excludePrefixes?: string[],
|
|
269
|
+
excludeDirNames?: Set<string>,
|
|
270
|
+
): void {
|
|
271
|
+
if (excludePrefixes) {
|
|
272
|
+
for (const prefix of excludePrefixes) {
|
|
273
|
+
if (currentPath === prefix || currentPath.startsWith(prefix + '/')) return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
228
277
|
if (node.kind === 'file') {
|
|
229
278
|
let data = '';
|
|
230
279
|
if (node.content && node.content.length > 0) {
|
|
@@ -237,8 +286,10 @@ export class MemoryVolume {
|
|
|
237
286
|
result.push({ path: currentPath, kind: 'directory' });
|
|
238
287
|
if (node.children) {
|
|
239
288
|
for (const [name, child] of node.children) {
|
|
289
|
+
// Skip excluded directory names at any depth (e.g. node_modules, .cache)
|
|
290
|
+
if (excludeDirNames && child.kind === 'directory' && excludeDirNames.has(name)) continue;
|
|
240
291
|
const childPath = currentPath === '/' ? `/${name}` : `${currentPath}/${name}`;
|
|
241
|
-
this.collectEntries(childPath, child, result);
|
|
292
|
+
this.collectEntries(childPath, child, result, excludePrefixes, excludeDirNames);
|
|
242
293
|
}
|
|
243
294
|
}
|
|
244
295
|
}
|
|
@@ -307,6 +358,10 @@ export class MemoryVolume {
|
|
|
307
358
|
// ---- Path utilities ----
|
|
308
359
|
|
|
309
360
|
private normalize(p: string): string {
|
|
361
|
+
if (this._handler) {
|
|
362
|
+
const cached = this._handler.pathNormCache.get(p);
|
|
363
|
+
if (cached !== undefined) return cached;
|
|
364
|
+
}
|
|
310
365
|
if (!p.startsWith('/')) p = '/' + p;
|
|
311
366
|
const parts = p.split('/').filter(Boolean);
|
|
312
367
|
const resolved: string[] = [];
|
|
@@ -314,7 +369,9 @@ export class MemoryVolume {
|
|
|
314
369
|
if (part === '..') resolved.pop();
|
|
315
370
|
else if (part !== '.') resolved.push(part);
|
|
316
371
|
}
|
|
317
|
-
|
|
372
|
+
const result = '/' + resolved.join('/');
|
|
373
|
+
if (this._handler) this._handler.pathNormCache.set(p, result);
|
|
374
|
+
return result;
|
|
318
375
|
}
|
|
319
376
|
|
|
320
377
|
// assumes pre-normalized input (starts with '/', no '..' or double slashes)
|
|
@@ -412,6 +469,8 @@ export class MemoryVolume {
|
|
|
412
469
|
modified: Date.now(),
|
|
413
470
|
});
|
|
414
471
|
|
|
472
|
+
if (this._handler) this._handler.invalidateStat(norm);
|
|
473
|
+
|
|
415
474
|
if (notify) {
|
|
416
475
|
this.triggerWatchers(norm, existed ? 'change' : 'rename');
|
|
417
476
|
this.broadcast('change', norm, typeof data === 'string' ? data : this.textDecoder.decode(data));
|
|
@@ -427,13 +486,19 @@ export class MemoryVolume {
|
|
|
427
486
|
|
|
428
487
|
statSync(p: string): FileStat {
|
|
429
488
|
const norm = this.normalize(p);
|
|
489
|
+
|
|
490
|
+
if (this._handler) {
|
|
491
|
+
const cached = this._handler.statCache.get(norm);
|
|
492
|
+
if (cached !== undefined) return cached;
|
|
493
|
+
}
|
|
494
|
+
|
|
430
495
|
const node = this.locate(norm);
|
|
431
496
|
if (!node) throw makeSystemError('ENOENT', 'stat', p);
|
|
432
497
|
|
|
433
498
|
const fileSize = node.kind === 'file' ? (node.content?.length || 0) : 0;
|
|
434
499
|
const ts = node.modified;
|
|
435
500
|
|
|
436
|
-
|
|
501
|
+
const result: FileStat = {
|
|
437
502
|
isFile: () => node.kind === 'file',
|
|
438
503
|
isDirectory: () => node.kind === 'directory',
|
|
439
504
|
isSymbolicLink: () => false,
|
|
@@ -464,6 +529,9 @@ export class MemoryVolume {
|
|
|
464
529
|
ctimeNs: BigInt(ts) * 1000000n,
|
|
465
530
|
birthtimeNs: BigInt(ts) * 1000000n,
|
|
466
531
|
};
|
|
532
|
+
|
|
533
|
+
if (this._handler) this._handler.statCache.set(norm, result);
|
|
534
|
+
return result;
|
|
467
535
|
}
|
|
468
536
|
|
|
469
537
|
lstatSync(p: string): FileStat {
|
|
@@ -573,6 +641,7 @@ export class MemoryVolume {
|
|
|
573
641
|
if (target.kind !== 'file') throw makeSystemError('EISDIR', 'unlink', p);
|
|
574
642
|
|
|
575
643
|
parent.children!.delete(name);
|
|
644
|
+
if (this._handler) this._handler.invalidateStat(norm);
|
|
576
645
|
this.triggerWatchers(norm, 'rename');
|
|
577
646
|
this.broadcast('delete', norm);
|
|
578
647
|
this.notifyGlobalListeners(norm, 'unlink');
|
|
@@ -594,6 +663,7 @@ export class MemoryVolume {
|
|
|
594
663
|
if (target.children!.size > 0) throw makeSystemError('ENOTEMPTY', 'rmdir', p);
|
|
595
664
|
|
|
596
665
|
parent.children!.delete(name);
|
|
666
|
+
if (this._handler) this._handler.invalidateStat(norm);
|
|
597
667
|
}
|
|
598
668
|
|
|
599
669
|
renameSync(from: string, to: string): void {
|
|
@@ -932,7 +1002,7 @@ export class MemoryVolume {
|
|
|
932
1002
|
const self = this;
|
|
933
1003
|
const pending: Uint8Array[] = [];
|
|
934
1004
|
const handlers: Record<string, ((...args: unknown[]) => void)[]> = {};
|
|
935
|
-
const enc =
|
|
1005
|
+
const enc = this.textEncoder;
|
|
936
1006
|
|
|
937
1007
|
return {
|
|
938
1008
|
write(data: string | Uint8Array): boolean {
|
|
@@ -13,6 +13,9 @@ import { downloadAndExtract } from "./archive-extractor";
|
|
|
13
13
|
import { convertPackage, prepareTransformer } from "../module-transformer";
|
|
14
14
|
import type { PackageManifest } from "../types/manifest";
|
|
15
15
|
import * as path from "../polyfills/path";
|
|
16
|
+
import type { IDBSnapshotCache } from "../persistence/idb-cache";
|
|
17
|
+
import { quickDigest } from "../helpers/digest";
|
|
18
|
+
import { base64ToBytes } from "../helpers/byte-encoding";
|
|
16
19
|
|
|
17
20
|
// ---------------------------------------------------------------------------
|
|
18
21
|
// Public types
|
|
@@ -87,11 +90,13 @@ export class DependencyInstaller {
|
|
|
87
90
|
private vol: MemoryVolume;
|
|
88
91
|
private registryClient: RegistryClient;
|
|
89
92
|
private workingDir: string;
|
|
93
|
+
private _snapshotCache: IDBSnapshotCache | null;
|
|
90
94
|
|
|
91
|
-
constructor(vol: MemoryVolume, opts: { cwd?: string } & RegistryConfig = {}) {
|
|
95
|
+
constructor(vol: MemoryVolume, opts: { cwd?: string; snapshotCache?: IDBSnapshotCache | null } & RegistryConfig = {}) {
|
|
92
96
|
this.vol = vol;
|
|
93
97
|
this.registryClient = new RegistryClient(opts);
|
|
94
98
|
this.workingDir = opts.cwd || "/";
|
|
99
|
+
this._snapshotCache = opts.snapshotCache ?? null;
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
// -----------------------------------------------------------------------
|
|
@@ -157,6 +162,37 @@ export class DependencyInstaller {
|
|
|
157
162
|
const raw = this.vol.readFileSync(jsonPath, "utf8");
|
|
158
163
|
const manifest: PackageManifest = JSON.parse(raw);
|
|
159
164
|
|
|
165
|
+
// Check IDB snapshot cache — skip full install if we have a cached node_modules
|
|
166
|
+
const cacheKey = this._snapshotCache ? quickDigest(raw) : null;
|
|
167
|
+
if (this._snapshotCache && cacheKey) {
|
|
168
|
+
try {
|
|
169
|
+
const cached = await this._snapshotCache.get(cacheKey);
|
|
170
|
+
if (cached) {
|
|
171
|
+
onProgress?.("Restoring cached node_modules...");
|
|
172
|
+
const { entries } = cached;
|
|
173
|
+
// Restore only node_modules entries from the snapshot
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
if (!entry.path.includes('/node_modules/')) continue;
|
|
176
|
+
if (entry.kind === 'directory') {
|
|
177
|
+
if (!this.vol.existsSync(entry.path)) {
|
|
178
|
+
this.vol.mkdirSync(entry.path, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
} else if (entry.kind === 'file' && entry.data) {
|
|
181
|
+
const parentDir = entry.path.substring(0, entry.path.lastIndexOf('/')) || '/';
|
|
182
|
+
if (parentDir !== '/' && !this.vol.existsSync(parentDir)) {
|
|
183
|
+
this.vol.mkdirSync(parentDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
this.vol.writeFileSync(entry.path, base64ToBytes(entry.data));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
onProgress?.(`Restored ${entries.length} cached entries`);
|
|
189
|
+
return { resolved: new Map(), newPackages: [] };
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Cache miss or error — proceed with normal install
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
160
196
|
onProgress?.("Resolving dependency tree...");
|
|
161
197
|
|
|
162
198
|
const resolutionOpts: ResolutionConfig = {
|
|
@@ -170,6 +206,18 @@ export class DependencyInstaller {
|
|
|
170
206
|
|
|
171
207
|
const newPkgs = await this.materializePackages(tree, flags);
|
|
172
208
|
|
|
209
|
+
// Cache the installed node_modules snapshot for future reuse
|
|
210
|
+
if (this._snapshotCache && cacheKey && newPkgs.length > 0) {
|
|
211
|
+
try {
|
|
212
|
+
const snapshot = this.vol.toSnapshot();
|
|
213
|
+
// Filter to only node_modules entries to keep cache lean
|
|
214
|
+
const nmSnapshot = {
|
|
215
|
+
entries: snapshot.entries.filter(e => e.path.includes('/node_modules/')),
|
|
216
|
+
};
|
|
217
|
+
await this._snapshotCache.set(cacheKey, nmSnapshot);
|
|
218
|
+
} catch { /* cache write failure is non-fatal */ }
|
|
219
|
+
}
|
|
220
|
+
|
|
173
221
|
onProgress?.(`Installed ${tree.size} package(s)`);
|
|
174
222
|
|
|
175
223
|
return { resolved: tree, newPackages: newPkgs };
|