import { BrowserFileIOAdapter, BrowserSystemAdapter, executeCode, RuntimeError, VirtualFileSystem, } from 'numbl'; import type { RunRequest, WorkerResponse } from './protocol'; // The numbl execution worker. executeCode is synchronous, so it must run off // the main thread; this mirrors numbl's own (unpublished) numbl-worker.ts in // its non-persistent mode: each run gets a fresh VFS seeded with the whole // project, the script's directory becomes the cwd / first search path, and // output + figures stream back as messages. Stopping a run is a hard kill — // the app terminates this worker and spawns a fresh one. const post = (message: WorkerResponse) => (self as unknown as Worker).postMessage(message); self.onmessage = (event: MessageEvent) => { const msg = event.data; if (msg.type !== 'run') { return; } // project files live under the VFS default cwd, /project const vfs = new VirtualFileSystem(); for (const file of msg.vfsFiles) { vfs.writeFile(file.path, file.content); } vfs.clearChangeTracking(); const fileIO = new BrowserFileIOAdapter(vfs); const system = new BrowserSystemAdapter(vfs); // run under the script's absolute VFS path with its directory as cwd, // mirroring the CLI `run` command — sibling functions and relative file // I/O then resolve against the script's folder const mainAbsPath = vfs.normalizePath(msg.mainFileName); const lastSlash = mainAbsPath.lastIndexOf('/'); vfs.setCwd(lastSlash > 0 ? mainAbsPath.slice(0, lastSlash) : '/'); try { const result = executeCode( msg.code, { onOutput: (text) => post({ type: 'output', text }), onDrawnow: (plotInstructions) => post({ type: 'drawnow', plotInstructions }), displayResults: true, maxIterations: 10_000_000, fileIO, system, }, msg.workspaceFiles, mainAbsPath, ); // onDrawnow flushes (and clears) the instruction buffer mid-run, so // result.plotInstructions is only the tail since the last drawnow post({ type: 'done', plotInstructions: result.plotInstructions, vfsChanges: fileIO.getChanges() }); } catch (error) { let message: string; if (error instanceof RuntimeError) { message = [error.toString(), error.snippet].filter(Boolean).join('\n'); } else { message = error instanceof Error ? error.message : String(error); } post({ type: 'error', message, vfsChanges: fileIO.getChanges() }); } };