31 lines
2.5 KiB
TypeScript
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;
|