Files
Endoleap/src/lib/canvas-runtime/resource-retention.ts
T

41 lines
3.2 KiB
TypeScript

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))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 [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,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)});
}