34 lines
4.4 KiB
JavaScript
34 lines
4.4 KiB
JavaScript
/** Imports compiled ESM directly: no TypeScript stripping or application bundler. */
|
|
import {createRegistry,builtinComponents,createDocument} from '/package/modules/canvas-core/index.js';
|
|
import {openProjectEngine} from '/package/modules/canvas-runtime/index.js';
|
|
import {createFoundryPersistence} from '/package/modules/canvas-service/index.js';
|
|
import {createBrowserStorage,createBrowserDigests,createBrowserImageDecoder} from '/package/modules/canvas-runtime/browser.js';
|
|
export async function run(){
|
|
const assert=(condition,message)=>{if(!condition)throw Error(message);},results=[];
|
|
const name=`headless-package-${crypto.randomUUID()}`,storage=createBrowserStorage({name}),digests=createBrowserDigests(),images=createBrowserImageDecoder();
|
|
const scope={accountId:crypto.randomUUID(),workspaceId:crypto.randomUUID(),projectId:crypto.randomUUID()},registry=createRegistry(builtinComponents()),document=createDocument(scope.projectId,[{id:scope.projectId,parent:null}],[]);
|
|
const workerUrls=[],NativeWorker=globalThis.Worker;let active=0;
|
|
globalThis.Worker=class extends NativeWorker{constructor(url,...args){super(url,...args);workerUrls.push(String(url));active++;this.live=true;}terminate(){if(this.live){this.live=false;active--;}super.terminate();}};
|
|
let engine;
|
|
try{
|
|
const canvas=new OffscreenCanvas(4,2);canvas.getContext('2d').fillRect(0,0,4,2);const bytes=await canvas.convertToBlob({type:'image/png'});
|
|
const dimensions=await images.decode(bytes);assert(dimensions.width===4&&dimensions.height===2,'compiled image Worker dimensions');
|
|
const digest=await digests.digest(bytes),expected=[...new Uint8Array(await crypto.subtle.digest('SHA-256',await bytes.arrayBuffer()))].map(value=>value.toString(16).padStart(2,'0')).join('');assert(digest===expected,'compiled digest Worker SHA256');
|
|
assert(workerUrls.some(url=>url.endsWith('/image.worker.js'))&&workerUrls.some(url=>url.endsWith('/digest.worker.js')),'Worker URLs did not resolve emitted JS');assert(active===0,'compiled Workers leaked');
|
|
results.push('compiled image and digest module Workers load emitted JS, decode a real PNG, match native SHA256 and terminate');
|
|
const largeBytes=new Blob([new Uint8Array(16*1024*1024).fill(97)]),largeExpected=[...new Uint8Array(await crypto.subtle.digest('SHA-256',await largeBytes.arrayBuffer()))].map(value=>value.toString(16).padStart(2,'0')).join(''),started=performance.now(),largeDigest=await digests.digest(largeBytes),digestMs=performance.now()-started;assert(largeDigest===largeExpected&&active===0,'large compiled digest returned wrong bytes or retained its Worker');results.push('16 MiB compiled digest Worker matched SHA256 in '+digestMs.toFixed(1)+' ms (observation, not an SLA)');
|
|
// Keep networking explicitly held: verify the actual browser durability adapter
|
|
// independently of an invented backend protocol implementation.
|
|
const persistence={...createFoundryPersistence({registry,rootId:scope.projectId,workspaceId:scope.workspaceId,transport:async()=>{throw Error('unexpected network');}}),supportsRequest:()=>false};
|
|
const open=()=>openProjectEngine({scope,registry,document,versions:new Map([[scope.projectId,1]]),storage,persistence,createId:()=>crypto.randomUUID()});
|
|
engine=await open();
|
|
const target={id:crypto.randomUUID(),incarnation:1};
|
|
assert(engine.editor.dispatch({type:'element.create',element:{...target,ownerCanvasId:scope.projectId,componentId:'text',dataVersion:1,structure:'node',data:{title:'Packed recovery',text:'persisted'},geometry:{x:12,y:24,width:160,height:80,rotation:0}}}).status==='applied','compiled command rejected');
|
|
engine.sessions.attach('temporary-view');
|
|
await engine.settle();const revision=engine.getSnapshot().rootRevision;await engine.close();engine=await open();
|
|
assert(engine.getSnapshot().rootRevision>=revision,'compiled IndexedDB recovery revision regressed');assert(engine.getSnapshot().document.elements.get(target.id)?.data.text==='persisted','compiled durable document lost');assert(engine.getSnapshot().history.undo.length===1,'compiled durable history lost');assert(engine.sessions.getSnapshot().views.size===0,'transient sessions persisted');await engine.settle();
|
|
results.push('compiled browser storage and engine claim/release Web Locks and recover an edited document/history from IndexedDB without persisting views');
|
|
return results;
|
|
}finally{await engine?.close();images.close();digests.close();storage.close();globalThis.Worker=NativeWorker;indexedDB.deleteDatabase(name);}
|
|
}
|