Reuse owned table entry views during recovery encoding
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
export * from './types.ts';
|
||||
export * from './values.ts';
|
||||
export {fault,reject,freeze,clone,equal,readMap,readSet,validJson} from './values.ts';
|
||||
export * from './document.ts';
|
||||
export * from './registry.ts';
|
||||
export * from './features.ts';
|
||||
|
||||
@@ -24,6 +24,9 @@ export function equal(a: unknown, b: unknown): boolean {
|
||||
const left = Object.keys(a), right = Object.keys(b);
|
||||
return left.length === right.length && left.every(key => Object.prototype.hasOwnProperty.call(b, key) && equal((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]));
|
||||
}
|
||||
const ownedMapViews=new WeakSet<object>();
|
||||
/** Internal provenance only; table ownership does not imply immutable values. */
|
||||
export function isOwnedMapView(value:object){return ownedMapViews.has(value);}
|
||||
/** A capability-safe read view: unlike TS ReadonlyMap, no .set is exposed at runtime. */
|
||||
export function readMap<K, V>(input: ReadonlyMap<K, V>): ReadonlyMap<K, V> {
|
||||
const map = new Map(input);
|
||||
@@ -33,7 +36,7 @@ export function readMap<K, V>(input: ReadonlyMap<K, V>): ReadonlyMap<K, V> {
|
||||
[Symbol.iterator]: () => map[Symbol.iterator](),
|
||||
forEach: (callback, thisArg) => map.forEach((value, key) => callback.call(thisArg, value, key, view)),
|
||||
};
|
||||
return Object.freeze(view);
|
||||
const result=Object.freeze(view);ownedMapViews.add(result);return result;
|
||||
}
|
||||
export function readSet<T>(input: Iterable<T>): ReadonlyValueSet<T> {
|
||||
const set = new Set(input);
|
||||
|
||||
@@ -14,13 +14,25 @@ import { validateHistoryReplay, replayHistoryEvidence, sameHistory } from './his
|
||||
import type { RecoveryAudit } from './conflicts.ts';
|
||||
import { applyHistoryTransition, validateHistoryTransition, type HistoryTransition } from '../canvas-core/history-transition.ts';
|
||||
import { createDocument, validateDocument, isFactoryDocument } from '../canvas-core/document.ts';
|
||||
import { fault, freeze, readMap, validJson, equal } from '../canvas-core/values.ts';
|
||||
import { fault, freeze, readMap, validJson, equal, isOwnedMapView } from '../canvas-core/values.ts';
|
||||
import type { EngineSnapshot, RuntimeState } from './contracts.ts';
|
||||
import type { FrozenRequest, LedgerSnapshot, OperationRecord } from './ledger.ts';
|
||||
import { validateRunInput, type RunRecord } from './runs.ts';
|
||||
import type { ResourceRecord } from './resources.ts';
|
||||
import type { RecoveryImage } from './storage-port.ts';
|
||||
|
||||
// Membership is stable only for core-owned read views. Never infer ownership
|
||||
// from Object.freeze: a frozen native Map can still change through .set().
|
||||
const entryViews=new WeakMap<object,readonly (readonly [unknown,unknown])[]>();
|
||||
const noEntries=Object.freeze([]);
|
||||
function mapValue<K,V>(map?:ReadonlyMap<K,V>):readonly (readonly [K,V])[]{
|
||||
if(!map)return noEntries;
|
||||
if(!isOwnedMapView(map))return [...map];
|
||||
let value=entryViews.get(map);
|
||||
if(!value){value=Object.freeze([...map].map(pair=>Object.freeze(pair)));entryViews.set(map,value);}
|
||||
return value as readonly (readonly [K,V])[];
|
||||
}
|
||||
|
||||
function encodeDocumentValue(document:Document){
|
||||
// Freeze our own containers only. The codec still independently proves whether
|
||||
// each nested value can be reused, including external mutable records.
|
||||
@@ -35,9 +47,9 @@ function documentValue(document:Document){
|
||||
export function encodeRecovery(snapshot: EngineSnapshot): RecoveryImage {
|
||||
const payload = {
|
||||
document: documentValue(snapshot.document),
|
||||
history: { undo: snapshot.history.undo, redo: snapshot.history.redo, entries: [...snapshot.history.entries] },
|
||||
operations: [...snapshot.operations],
|
||||
runtime: { ...(snapshot.runtime.assetBindings?.size?{assetBindings:[...snapshot.runtime.assetBindings]}:{}), ...(snapshot.runtime.loadingRead?{loadingRead:{document:documentValue(snapshot.runtime.loadingRead.document),versions:[...snapshot.runtime.loadingRead.versions]}}:{}), ...(snapshot.runtime.loading?{loading:snapshot.runtime.loading}:{}), ...(snapshot.runtime.orphanFiles?.size?{orphanFiles:[...snapshot.runtime.orphanFiles]}:{}), imports:[...(snapshot.runtime.imports??[])], recoveries:[...(snapshot.runtime.recoveries??[])], runs: [...snapshot.runtime.runs], resources: [...snapshot.runtime.resources], resourcePins: [...snapshot.runtime.resourcePins], acknowledged: documentValue(snapshot.runtime.acknowledged), versions: [...snapshot.runtime.versions], receipts: [...snapshot.runtime.receipts], conflicts: [...snapshot.runtime.conflicts], ledger: { ...snapshot.runtime.ledger, operations: [...snapshot.runtime.ledger.operations], requests: [...snapshot.runtime.ledger.requests] } },
|
||||
history: { undo: snapshot.history.undo, redo: snapshot.history.redo, entries: mapValue(snapshot.history.entries) },
|
||||
operations: mapValue(snapshot.operations),
|
||||
runtime: { ...(snapshot.runtime.assetBindings?.size?{assetBindings:mapValue(snapshot.runtime.assetBindings)}:{}), ...(snapshot.runtime.loadingRead?{loadingRead:{document:documentValue(snapshot.runtime.loadingRead.document),versions:mapValue(snapshot.runtime.loadingRead.versions)}}:{}), ...(snapshot.runtime.loading?{loading:snapshot.runtime.loading}:{}), ...(snapshot.runtime.orphanFiles?.size?{orphanFiles:mapValue(snapshot.runtime.orphanFiles)}:{}), imports:mapValue(snapshot.runtime.imports), recoveries:mapValue(snapshot.runtime.recoveries), runs: mapValue(snapshot.runtime.runs), resources: mapValue(snapshot.runtime.resources), resourcePins: mapValue(snapshot.runtime.resourcePins), acknowledged: documentValue(snapshot.runtime.acknowledged), versions: mapValue(snapshot.runtime.versions), receipts: mapValue(snapshot.runtime.receipts), conflicts: mapValue(snapshot.runtime.conflicts), ledger: { ...snapshot.runtime.ledger, operations: mapValue(snapshot.runtime.ledger.operations), requests: mapValue(snapshot.runtime.ledger.requests) } },
|
||||
};
|
||||
return sealRecoveryImage([...(snapshot.runtime.imports?.values()??[])].some(job=>job.source?.integrityVersion===2)?17:[...(snapshot.runtime.imports?.values()??[])].some(job=>job.source?.kind==='native-document'&&job.parserVersion===5)?16:[...(snapshot.runtime.imports?.values()??[])].some(job=>job.source?.kind==='native-document')?15:[...(snapshot.runtime.imports?.values()??[])].some(job=>job.source?.kind==='native-json-array')?14:snapshot.runtime.assetBindings?.size?13:snapshot.runtime.loading?(snapshot.runtime.loading.version===6?12:snapshot.runtime.loading.version===5?11:snapshot.runtime.loading.version===4?10:snapshot.runtime.loading.version===3?9:snapshot.runtime.loading.version===2?8:7):[...snapshot.operations.values()].some(intent=>intent.historyEffect)||[...snapshot.runtime.resources.values()].some(resource=>resource.sizing)?6:snapshot.runtime.orphanFiles?.size?5:[...(snapshot.runtime.imports?.values()??[])].some(job=>job.source?.retirement||job.source?.previousFiles?.length)?4:[...snapshot.runtime.resources.values()].some(resource=>resource.retirement)?3:snapshot.runtime.imports?.size ? 2 : 1,snapshot.rootRevision,snapshot.revision,payload);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user