wasm-worker.js (6464B)
1 import { importPlan } from './import-plan.js'; 2 import { localModuleInputs, validateLocalModule } from './local-modules.js'; 3 import { resumeValue } from './io.js'; 4 import { createRuntimeSession } from './runtime-session.js'; 5 const selectedBackend = new URL(globalThis.location.href).searchParams.get('backend'); 6 import { publishedBundle } from './published-bundle.js'; 7 import { bundleHash } from './bundle.js'; 8 import { literals, outputType } from './codecs.js'; 9 import { sourceText } from './compiler.js'; 10 import { exportTree, treeFormat } from './tree-export.js'; 11 const scope = globalThis; 12 const send = (m) => scope.postMessage(m); 13 const running = () => send({ type: 'state', state: 'running' }); 14 let session, stored; 15 let busy = false, requestId = 0; 16 let pending; 17 function request(kind) { 18 if (pending) 19 throw new Error('Driver already waiting'); 20 const id = ++requestId; 21 return new Promise((resolve, reject) => { pending = { id, resolve, reject, kind }; send({ type: 'state', state: kind, id }); }); 22 } 23 scope.onmessage = ({ data }) => { 24 if (!data || typeof data !== 'object' || Array.isArray(data)) 25 return; 26 const m = data; 27 if (m['type'] === 'resume') { 28 if (!pending || m['id'] !== pending.id) 29 return; 30 const value = resumeValue(pending.kind, m['value']); 31 if (value === undefined) 32 return; 33 const waiter = pending; 34 pending = undefined; 35 waiter.resolve(value); 36 return; 37 } 38 if (busy) 39 return; 40 busy = true; 41 void (async () => { 42 if (m['type'] === 'check-import') { 43 if (session) 44 throw new Error('Already initialized'); 45 const source = sourceText(m['source']); 46 if (typeof m['manifest'] !== 'string' || typeof m['alias'] !== 'string') 47 throw new Error('Invalid import request'); 48 session = await createRuntimeSession(selectedBackend, true); 49 const stage = await session.inspectModule(source); 50 const plan = importPlan(source, stage, m['manifest'], m['alias']); 51 if (!plan.existing) 52 await session.compile(plan.source, 'empty', localModuleInputs(m['localModules'])); 53 send({ type: 'import-checked', plan }); 54 } 55 else if (m['type'] === 'load') { 56 if (session || stored) 57 throw new Error('Already initialized'); 58 const localId = m['localId']; 59 if (localId !== undefined && (typeof localId !== 'string' || localId.length > 128)) 60 throw new Error('Invalid catalogue ID'); 61 running(); 62 stored = await publishedBundle(bundleHash(m['hash']), localId); 63 session = await createRuntimeSession(selectedBackend, false); 64 await session.load(stored.bytes, stored.hash); 65 send({ type: 'loaded', hash: stored.hash, exports: stored.bundle.exports, nodes: stored.bundle.nodes.length, metadata: stored.bundle.metadata, source: stored.source }); 66 } 67 else if (m['type'] === 'compile') { 68 if (session) 69 throw new Error('Already initialized'); 70 const source = sourceText(m['source']), profile = m['environment']; 71 if (profile !== undefined && (typeof profile !== 'string' || profile.length > 128)) 72 throw new Error('Invalid demo environment'); 73 running(); 74 const start = performance.now(); 75 session = await createRuntimeSession(selectedBackend, true); 76 const names = await session.compile(source, profile, localModuleInputs(m['localModules'])); 77 send({ type: 'compiled', names, stats: session.stats(), milliseconds: performance.now() - start }); 78 } 79 else if (m['type'] === 'export-module') { 80 if (!session || stored) 81 throw new Error('Compile a draft first'); 82 running(); 83 try { 84 const module = await validateLocalModule(await session.exportModule()); 85 send({ type: 'module-exported', bytes: module.bytes, hash: module.hash, manifest: module.manifest }); 86 } 87 catch (e) { 88 if (session.disposed) 89 throw e; 90 send({ type: 'module-export-error', message: String(e) }); 91 } 92 } 93 else if (m['type'] === 'run') { 94 if (!session) 95 throw new Error('Load or compile first'); 96 if (typeof m['export'] !== 'string' || m['export'].length > 512) 97 throw new Error('Invalid export'); 98 if (m['mode'] !== 'pure' && m['mode'] !== 'io') 99 throw new Error('Unsupported execution mode'); 100 const args = literals(m['arguments']), output = outputType(m['output']); 101 running(); 102 const host = { 103 async before(event) { send({ type: 'trace', phase: 'before', event }); if (m['step'] === true) 104 await request('step'); running(); }, 105 after(event) { send({ type: 'trace', phase: 'after', event }); }, write(text) { send({ type: 'output', text }); }, 106 read() { return request('input'); }, running, 107 }; 108 const result = m['expression'] === undefined 109 ? await session.run(m['export'], args, output, m['mode'], host) 110 : await session.runExpression(sourceText(m['expression']), output, m['mode'], host); 111 send({ type: 'done', result, stats: session.stats() }); 112 } 113 else if (m['type'] === 'export' && stored) { 114 try { 115 if (typeof m['export'] !== 'string') 116 throw new Error('Invalid export'); 117 const format = treeFormat(m['format']); 118 send({ type: 'exported', hash: stored.hash, export: m['export'], format, file: exportTree(stored.bundle, m['export'], format) }); 119 } 120 catch (e) { 121 send({ type: 'export-error', message: String(e) }); 122 } 123 } 124 else if (m['type'] === 'download' && stored) 125 send({ type: 'downloaded', hash: stored.hash, bytes: stored.bytes }); 126 else 127 throw new Error('Unsupported backend operation'); 128 })().catch((e) => send({ type: 'error', message: e instanceof Error ? e.message : String(e), fatal: session?.disposed ?? true })) 129 .finally(() => { busy = false; }); 130 };