worker.js (4502B)
1 // Test-only JS evaluator driver; excluded from the production site. 2 import { sourceText } from '../compiler.js'; 3 import { compileExpression } from './expression.js'; 4 import { publishedBundle } from '../published-bundle.js'; 5 import { exportTree, treeFormat } from '../tree-export.js'; 6 import { BROWSER_BUDGET, collectionPolicy, PACKAGE_COLLECTION } from '../execution.js'; 7 import { Graph } from '../graph.js'; 8 import { bundleHash, loadBundle } from '../bundle.js'; 9 import { literals, makeLiteral, outputType, view } from '../codecs.js'; 10 const scope = globalThis; 11 const send = (message) => scope.postMessage(message); 12 const running = () => send({ type: 'state', state: 'running' }); 13 let phase = 'empty'; 14 let graph, exports; 15 let stored; 16 async function load(hash, localId) { 17 phase = 'loading'; 18 running(); 19 const loaded = await publishedBundle(hash, localId); 20 const { bundle, source } = loaded; 21 stored = loaded; 22 const budget = BROWSER_BUDGET; 23 graph = new Graph(budget, collectionPolicy(budget, PACKAGE_COLLECTION.threshold)); 24 exports = loadBundle(graph, bundle); 25 phase = 'ready'; 26 send({ type: 'loaded', hash, exports: bundle.exports, nodes: bundle.nodes.length, metadata: bundle.metadata, source }); 27 } 28 async function run(message) { 29 if (phase !== 'ready' || !graph || !exports) 30 throw new Error('Load a bundle before running'); 31 phase = 'running'; 32 running(); 33 const fn = message.expression === undefined ? exports.get(message.export) : await compileExpression(graph, exports, message.expression); 34 if (fn === undefined) 35 throw new Error('Unknown export'); 36 if (message.mode !== 'pure' && message.mode !== 'io') 37 throw new Error('Unsupported execution mode'); 38 const type = outputType(message.output), args = literals(message.arguments).map(value => makeLiteral(graph, value)); 39 let result = message.expression === undefined ? graph.apply(fn, args) : fn; 40 graph.collect([result]); // Exclusive one-run session: only this application remains owned. 41 if (message.mode === 'io') 42 throw new Error('Reference graph IO execution is retired; use a native runtime'); 43 const displayed = view(graph, result, type); 44 phase = 'done'; 45 send({ type: 'done', result: displayed, stats: graph.stats }); 46 } 47 scope.onmessage = ({ data }) => { 48 if (!data || typeof data !== 'object' || Array.isArray(data)) 49 return; 50 const message = data; 51 if (message['type'] === 'download' && phase === 'ready' && stored) { 52 send({ type: 'downloaded', hash: stored.hash, bytes: stored.bytes }); 53 return; 54 } 55 if (message['type'] === 'export' && phase === 'ready' && stored) { 56 try { 57 if (typeof message['export'] !== 'string' || message['export'].length > 512) 58 throw new Error('Invalid export selection'); 59 const format = treeFormat(message['format']); 60 const file = exportTree(stored.bundle, message['export'], format); 61 send({ type: 'exported', hash: stored.hash, export: message['export'], format, file }); 62 } 63 catch (error) { 64 send({ type: 'export-error', message: error instanceof Error ? error.message : String(error) }); 65 } 66 return; 67 } 68 let operation; 69 try { 70 if (message['type'] === 'load' && phase === 'empty') { 71 const localId = message['localId']; 72 if (localId !== undefined && (typeof localId !== 'string' || localId.length > 128)) 73 throw new Error('Invalid catalogue ID'); 74 operation = load(bundleHash(message['hash']), localId); 75 } 76 else if (message['type'] === 'run' && phase === 'ready') { 77 if (typeof message['export'] !== 'string' || message['export'].length > 512) 78 throw new Error('Invalid export selection'); 79 operation = run({ type: 'run', export: message['export'], arguments: message['arguments'], 80 ...(message['expression'] === undefined ? {} : { expression: sourceText(message['expression']) }), 81 output: outputType(message['output']), mode: message['mode'], step: message['step'] === true }); 82 } 83 else 84 return; // A Worker owns exactly one load and one run, no racing sessions. 85 } 86 catch (error) { 87 operation = Promise.reject(error); 88 } 89 operation.catch((error) => { phase = 'done'; send({ type: 'error', message: error instanceof Error ? error.message : String(error) }); }); 90 };