Reuse validated immutable history ranges during resource retention
This commit is contained in:
@@ -5,6 +5,24 @@ type Splice<T>={readonly offset:number;readonly beforeLength:number;readonly aft
|
||||
type Meta={readonly label:string;readonly groupId?:Id};
|
||||
type EntryPatch={readonly id:Id;readonly before:Meta|null;readonly after:Meta|null;readonly changes:Splice<Change>;readonly operationIds:Splice<Id>};
|
||||
export type HistoryTransition={readonly version:1;readonly rootRevision:number;readonly entryId?:Id;readonly before:{readonly undo:readonly Id[];readonly redo:readonly Id[]};readonly after:{readonly undo:readonly Id[];readonly redo:readonly Id[]};readonly entries:readonly EntryPatch[]};
|
||||
// Resource scans revisit the same transition across many durability cuts. Cache
|
||||
// only its flattened changes, never registry-dependent references or callbacks.
|
||||
const transitionChanges=new WeakMap<HistoryTransition,readonly Change[]>();
|
||||
const immutableData=new WeakSet<object>();
|
||||
function immutablePlainData(value:unknown,depth=0):boolean{
|
||||
if(depth>64)return false;
|
||||
if(value===null||typeof value!=='object')return typeof value!=='function';
|
||||
if(immutableData.has(value))return true;
|
||||
if(!Object.isFrozen(value)||Object.getPrototypeOf(value)!==(Array.isArray(value)?Array.prototype:Object.prototype))return false;
|
||||
if(Array.isArray(value))for(let i=0;i<value.length;i++)if(!Object.hasOwn(value,i))return false;
|
||||
// Inspect every own descriptor, including non-enumerable properties. A frozen
|
||||
// getter or a shallow-frozen container cannot establish reusable data.
|
||||
for(const key of Reflect.ownKeys(value)){
|
||||
const descriptor=Object.getOwnPropertyDescriptor(value,key)!;
|
||||
if(!Object.hasOwn(descriptor,'value')||!immutablePlainData(descriptor.value,depth+1))return false;
|
||||
}
|
||||
immutableData.add(value);return true;
|
||||
}
|
||||
const meta=(entry:HistoryEntry|undefined):Meta|null=>{if(!entry)return null;const{id:_id,changes:_changes,operationIds:_operations,...remaining}=entry;return remaining;};
|
||||
function splice<T>(before:readonly T[],after:readonly T[]):Splice<T>{
|
||||
let offset=0,suffix=0;while(offset<before.length&&offset<after.length&&equal(before[offset],after[offset]))offset++;
|
||||
@@ -43,4 +61,11 @@ export function applyHistoryTransition(history:HistoryState,transition:HistoryTr
|
||||
const ids=[...destination.undo,...destination.redo];if(ids.length!==entries.size||ids.some(id=>!entries.has(id)))throw fault('history_transition_conflict');
|
||||
return Object.freeze({undo:freeze([...destination.undo]),redo:freeze([...destination.redo]),entries:readMap(entries)});
|
||||
}
|
||||
export function historyTransitionChanges(transition:HistoryTransition):readonly Change[]{validateHistoryTransition(transition);return transition.entries.flatMap(patch=>[...patch.changes.before,...patch.changes.after]);}
|
||||
export function historyTransitionChanges(transition:HistoryTransition):readonly Change[]{
|
||||
const cached=transitionChanges.get(transition);if(cached)return cached;
|
||||
validateHistoryTransition(transition);
|
||||
const changes=transition.entries.flatMap(patch=>[...patch.changes.before,...patch.changes.after]);
|
||||
const ownRanges=Object.hasOwn(transition,'entries')&&transition.entries.every(patch=>Object.hasOwn(patch,'changes')&&Object.hasOwn(patch.changes,'before')&&Object.hasOwn(patch.changes,'after'));
|
||||
if(!ownRanges||!immutablePlainData(transition))return changes;
|
||||
const result=Object.freeze(changes);transitionChanges.set(transition,result);return result;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export function createResourcePinScan(history:HistoryState,ledger:LedgerSnapshot
|
||||
for(const entry of history.entries.values())rememberTargets(entry.changes);
|
||||
let interpreted=true;
|
||||
for(const record of ledger.operations.values()){
|
||||
// Validate every understood transition, including resolved evidence, as before.
|
||||
// Obtain validated ranges for every understood transition, including resolved
|
||||
// evidence. The core may reuse only proven immutable transition data.
|
||||
if(record.intent.historyTransition?.version===1)transitions.set(record,historyTransitionChanges(record.intent.historyTransition));
|
||||
if(record.phase!=='resolved')rememberTargets(record.intent.changes);
|
||||
if(record.intent.historyEffect&&record.intent.historyEffect.version!==1)interpreted=false;
|
||||
|
||||
Reference in New Issue
Block a user