Files
Endoleap/src/lib/canvas-core/creation-draft.ts
T

31 lines
2.5 KiB
TypeScript

import type {CanvasRecord,Change,Document,ElementRecord,Id} from './types.ts';
import {applyChanges} from './document.ts';
import {clone,fault,freeze,readMap,validJson} from './values.ts';
/** Transaction-local append views. Each retained view has fixed membership;
* at most 64 additions are copied before folding into a new read-only base. */
const overlays=new WeakMap<object,{base:ReadonlyMap<Id,unknown>;added:ReadonlyMap<Id,unknown>}>(),drafts=new WeakSet<Document>();
function append<T>(input:ReadonlyMap<Id,T>,id:Id,value:T):ReadonlyMap<Id,T>{
if(input.has(id))throw fault('duplicate_identity',[id]);
let previous=overlays.get(input) as {base:ReadonlyMap<Id,T>;added:ReadonlyMap<Id,T>}|undefined;
if(previous&&previous.added.size>=64)previous=undefined;
const base=previous?.base??(overlays.has(input)?readMap(input):input),added=new Map(previous?.added);
added.set(id,value);
const entries=function*():Generator<[Id,T],undefined,unknown>{yield* base;yield* added;return undefined;};
const view:ReadonlyMap<Id,T>=Object.freeze({size:base.size+added.size,get:(key:Id)=>added.has(key)?added.get(key):base.get(key),has:(key:Id)=>added.has(key)||base.has(key),entries,
*keys():Generator<Id,undefined,unknown>{for(const [key]of entries())yield key;return undefined;},*values():Generator<T,undefined,unknown>{for(const [,item]of entries())yield item;return undefined;},[Symbol.iterator]:entries,
forEach(callback:(value:T,key:Id,map:ReadonlyMap<Id,T>)=>void,thisArg?:unknown){for(const[key,item]of entries())callback.call(thisArg,item,key,view);},
});
overlays.set(view,{base,added});return view;
}
/** Internal editor path: input tables already have fixed membership. */
export function stageCreation(document:Document,change:Extract<Change,{kind:'canvas'|'element'}>):Document{
if(change.before!==null||!change.after)throw fault('invalid_creation_draft');
if(!validJson(change.after))throw fault(change.kind==='canvas'?'invalid_canvas':'invalid_element',[change.id]);
if(change.after.id!==change.id)throw fault('identity_mismatch',[change.id]);
const value=freeze(clone(change.after));
const next=Object.freeze({...document,...(change.kind==='canvas'?{canvases:append(document.canvases,change.id,value as CanvasRecord)}:{elements:append(document.elements,change.id,value as ElementRecord)})});drafts.add(next);return next;
}
/** Only published documents use the core's owned table envelopes. */
export const materializeCreations=(document:Document)=>drafts.has(document)?applyChanges(document,[]):document;