43 lines
3.7 KiB
TypeScript
43 lines
3.7 KiB
TypeScript
import {importNeedsSeal} from './imports.ts';
|
|
import type {Id,Registry} from '../canvas-core/types.ts';
|
|
import {fault,readMap,readSet,freeze} from '../canvas-core/values.ts';
|
|
import type {EngineSnapshot,RuntimeState} from './contracts.ts';
|
|
import {resourcePins} from './resources.ts';
|
|
|
|
/** Complete local-byte protection for one project scope; no DOM/viewport filtering. */
|
|
export function resourceRetentionPins(snapshot:EngineSnapshot,registry:Registry,displays:ReadonlyMap<Id,number>=new Map(),temporary:ReadonlyMap<Id,readonly Id[]>=new Map()) {
|
|
const {runtime}=snapshot;
|
|
const current=resourcePins(snapshot,runtime.ledger,registry,runtime.resourcePins);
|
|
const base=resourcePins({...snapshot,document:runtime.acknowledged},runtime.ledger,registry,new Map());
|
|
const pins=new Map<Id,Set<string>>();
|
|
const add=(id:Id,owner:string)=>{let owners=pins.get(id);if(!owners){owners=new Set();pins.set(id,owners);}owners.add(owner);};
|
|
for(const [id,owners]of current.pins)for(const owner of owners)add(id,owner);
|
|
for(const [id,owners]of base.pins)for(const owner of owners)if(owner.startsWith('element:'))add(id,`acknowledged:${owner}`);
|
|
// Sources and server run envelopes are not all interpreted resource schemas yet.
|
|
// Pin the whole known local depot rather than claiming opaque task data contains no references.
|
|
for(const job of runtime.imports?.values()??[])if(!['completed','partial','cancelled'].includes(job.status)||importNeedsSeal(job)){if(job.source)add(job.source.fileId,`import-source:${job.id}`);for(const [id,resource]of runtime.resources)if(resource.retirement?.purgedAt===undefined)add(id,`import:${job.id}`);}
|
|
for(const run of runtime.runs.values())for(const [id,resource]of runtime.resources)if(resource.retirement?.purgedAt===undefined)add(id,`run:${run.id}`);
|
|
for(const resource of runtime.resources.values())if(resource.image&&(resource.image.version!==1||resource.image.status==='pending'))add(resource.id,resource.image.version!==1?'image-decoder-unavailable':'image-decode');
|
|
for(const [owner,ids]of temporary)for(const id of ids)add(id,owner);
|
|
for(const [id,count]of displays)if(count>0)add(id,'display');
|
|
return Object.freeze({complete:current.complete&&base.complete&&![...runtime.resources.values()].some(resource=>resource.image&&resource.image.version!==1||resource.sizing&&resource.sizing.version!==1),pins:readMap(new Map([...pins].map(([id,owners])=>[id,readSet(owners)])))});
|
|
}
|
|
|
|
/** Runs inside the editor's single publication boundary, including reconcile and metadata commits. */
|
|
export function prepareResourceRetention(next:EngineSnapshot,registry:Registry,displays:ReadonlyMap<Id,number>,reserved:ReadonlySet<Id>,temporary:ReadonlyMap<Id,readonly Id[]>=new Map()):RuntimeState {
|
|
if(!reserved.size&&![...next.runtime.resources.values()].some(resource=>resource.retirement&&(resource.retirement.purgedAt===undefined||!resource.stored)))return next.runtime;
|
|
const pins=resourceRetentionPins(next,registry,displays,temporary);
|
|
if(reserved.size&&(!pins.complete||[...reserved].some(id=>pins.pins.has(id))))throw fault('resource_collection_in_progress',[...reserved]);
|
|
let resources=next.runtime.resources;
|
|
for(const [id,resource]of next.runtime.resources){
|
|
if(!resource.retirement)continue;
|
|
if(resource.retirement.purgedAt!==undefined){
|
|
if(!resource.stored&&pins.pins.has(id))throw fault('resource_bytes_unavailable',[id]);
|
|
}else if(!pins.complete||pins.pins.has(id)){
|
|
if(resources===next.runtime.resources)resources=new Map(resources);
|
|
const {retirement:_,...kept}=resource;(resources as Map<Id,typeof resource>).set(id,freeze(kept));
|
|
}
|
|
}
|
|
return resources===next.runtime.resources?next.runtime:Object.freeze({...next.runtime,resources:readMap(resources)});
|
|
}
|