Stage transaction creations without copying full tables per command
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
@@ -1,3 +1,4 @@
|
||||
import {stageCreation,materializeCreations} from './creation-draft.ts';
|
||||
import type { Applied, Availability, Capability, Change, Command, Commit, CoreSnapshot, Document, ElementRecord, Geometry, HistoryEntry, HistoryState, Id, OperationIntent, Origin, Registry, Result, Snapshot } from './types.ts';
|
||||
import { applyChanges, createDocument, invert, targetElement, validateDocument } from './document.ts';
|
||||
import { clone, equal, fault, freeze, readMap, reject, validJson } from './values.ts';
|
||||
@@ -117,7 +118,7 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
|
||||
changes = output;
|
||||
}
|
||||
}
|
||||
const next = applyChanges(document, changes);
|
||||
const next = command.type==='element.create'||command.type==='canvas.create'?stageCreation(document,changes[0] as Extract<Change,{kind:'canvas'|'element'}>):applyChanges(document, changes);
|
||||
return { document: next, changes: freeze(clone(changes)) };
|
||||
}
|
||||
|
||||
@@ -221,6 +222,7 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
|
||||
active = false;
|
||||
if (isThenable(result)) { Promise.resolve(result).catch(() => {}); throw fault('async_transaction'); }
|
||||
if (failed) throw failed;
|
||||
document=materializeCreations(document);
|
||||
validateDocument(document, options.registry);
|
||||
if (!changes.length || documentsEqual(document, state.document)) return Object.freeze({ status: 'applied', revision: state.revision, operationIds: Object.freeze([]) });
|
||||
let history = state.history, historyEntryId: Id | undefined;
|
||||
|
||||
Reference in New Issue
Block a user