asset-cache-probe.mjs (4629B)
1 // Count requests at the server: cached DevTools entries are not network traffic. 2 const el=id=>document.getElementById(id),delay=ms=>new Promise(r=>setTimeout(r,ms)); 3 const assert=(ok,message)=>{if(!ok)throw Error(message);}; 4 async function status(wanted){for(let i=0;i<4500;i++){if(el('status').textContent===wanted)return;await delay(20);}throw Error('Waiting for '+wanted+': '+el('status').textContent+' '+el('compile-diagnostic').textContent);} 5 const change=(id,value)=>{el(id).value=value;el(id).dispatchEvent(new Event('change'));}; 6 const input=(id,value)=>{el(id).value=value;el(id).dispatchEvent(new Event('input'));}; 7 export async function run(control,backend='haskell-wasm'){ 8 await status('Ready'); 9 if(el('runtime-backend').value!==backend){change('runtime-backend',backend);await status('Ready');} 10 const base=new URL('../',document.querySelector('script[type=module][src]').src); 11 assert(/\/assets\/[a-f0-9]{64}\/$/.test(base.pathname),'Testing a versioned release'); 12 const html=await fetch(new URL('index.html?noProbe=1',location.href)); 13 assert(html.headers.get('Cache-Control')==='no-cache','HTML selects the current release through revalidation'); 14 const metadata=await fetch(new URL('compiler.json',base)); 15 assert(metadata.headers.get('Cache-Control')==='public, max-age=31536000, immutable','Metadata shares the immutable release'); 16 const requests=async()=>await(await fetch(control+'/requests')).json(); 17 const code=paths=>paths.filter(p=>p.startsWith(base.pathname)&&/\.(?:js|mjs|wasm)(?:\?|$)/.test(p)); 18 const evaluate=async()=>{input('expression-editor','42');change('output-type','number');change('mode','pure');el('run').click();await status('Finished');assert(el('result').textContent==='42','Evaluation works');}; 19 await evaluate(); // Warm the compiler and expression path too. 20 const warm=code(await requests());assert(warm.length>=5,'Network observed the initial dependency graph'); 21 for(const name of ['rgb','microscope','names-values']){ 22 change('program-picker','demo:'+name);await status('Ready');await evaluate(); 23 } 24 input('source-editor','!export answer\nanswer = 42');await status('Compiled'); 25 await evaluate(); 26 // A compile may introduce additional code; warm it before the repeat check. 27 const compiled=code(await requests()); 28 input('source-editor','!export answer\nanswer = 43');await status('Compiled');await evaluate(); 29 input('expression-editor','(x : x x) (x : x x)');el('run').click();await status('Running'); 30 el('stop-run').click();await status('Stopped'); 31 input('source-editor','!export answer\nanswer = 44');await status('Compiled');await evaluate(); 32 // The separately cancellable local-file validator has its own module Worker. 33 const catalogue=await(await fetch(new URL('catalogue.json',base))).json(); 34 const bytes=await(await fetch('/_arboricx/bundle/hash/'+catalogue[0].hash)).arrayBuffer(); 35 const open=()=>{const transfer=new DataTransfer();transfer.items.add(new File([bytes],'module.arboricx'));el('local-module-file').files=transfer.files;el('local-module-file').dispatchEvent(new Event('change'));}; 36 const checked=async()=>{for(let i=0;i<3000;i++){if(!el('local-module-file').disabled&&el('local-module-list').children.length===1)return;await delay(20);}throw Error('Local validation failed');}; 37 // Check runtime code reuse before warming the additional validator entrypoint. 38 const afterStop=code(await requests()); 39 assert(JSON.stringify(afterStop)===JSON.stringify(compiled),'Fresh compile/Stop Workers reuse cached code: '+JSON.stringify(afterStop.slice(compiled.length))); 40 open();await checked();const localWarm=code(await requests());open();await checked(); 41 const final=code(await requests()); 42 assert(JSON.stringify(final)===JSON.stringify(localWarm),'Local validator Workers reuse cached code: '+JSON.stringify(final.slice(localWarm.length))); 43 44 const counts=new Map();for(const p of final)counts.set(p,(counts.get(p)||0)+1); 45 assert([...counts.values()].every(n=>n===1),'Demo changes and fresh Workers fetch each code URL once: '+JSON.stringify([...counts].filter(([,n])=>n>1))); 46 // Errors remain retryable; an unknown release must not alias current bytes. 47 const missing=new URL(base.href.replace(/assets\/[a-f0-9]{64}/,'assets/'+'0'.repeat(64))+'dist/app.js'); 48 const response=await fetch(missing);assert(response.status===404,'Unknown release is missing');assert(response.headers.get('Cache-Control')==='no-store','Errors are not cached'); 49 return {userAgent:navigator.userAgent,checks:['versioned graph; demo switching; Stop/recompile; local validation; each code URL fetched once; missing release no-store'],codeRequests:final.length}; 50 }