/** * 同步事务内的大批创建优化。追加表用固定成员的 base+added 视图避免每新增一项都复制整表, * 已有追加层达到 64 项时,下一次追加先折叠为新 base。只用于未发布 draft,提交前统一物化文档表。 * 输入表须已具固定成员;视图借用 base 和未变记录,不是任意可变 Map 的隔离器。 */ import {detachElementData} from './element-data.ts'; 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;added:ReadonlyMap}>(),drafts=new WeakSet(); /** 在不可变输入表上创建新的追加视图;复制 added 而不改旧视图,保留之前 draft 的快照语义。 */ function append(input:ReadonlyMap,id:Id,value:T):ReadonlyMap{ if(input.has(id))throw fault('duplicate_identity',[id]); let previous=overlays.get(input) as {base:ReadonlyMap;added:ReadonlyMap}|undefined; // 当前 input 若含 64 个 added,本次先通过 readMap(input) 折叠,再以新 added 放入第 65 个;不是整个事务最多 64 项。 if(previous&&previous.added.size>=64)previous=undefined; // overlays 记录的是框架创建的视图身份;普通输入直接借用,只有已知旧 overlay 才需要折叠。 const base=previous?.base??(overlays.has(input)?readMap(input):input),added=new Map(previous?.added); added.set(id,value); /** 迭代先 base 后 added,保持插入顺序;视图的 get/has 优先查 appended 成员。 */ const entries=function*():Generator<[Id,T],undefined,unknown>{yield* base;yield* added;return undefined;}; // get 用 has 区分“added 中显式存 undefined”与缺失;迭代顺序始终 base 在前、追加成员在后。 // keys/values/iterator 复用 entries;forEach 第三个参数仍是安全视图,不泄漏 added Map。 const view:ReadonlyMap=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{for(const [key]of entries())yield key;return undefined;},*values():Generator{for(const [,item]of entries())yield item;return undefined;},[Symbol.iterator]:entries, forEach(callback:(value:T,key:Id,map:ReadonlyMap)=>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. */ /** 仅接收 before=null 且 after 存在的创建变化,校验 JSON/ID 后捕获记录并追加相应表。 * 整图依赖可在后续命令补齐,因此此处不提前做完整树/端点校验。 * 本层只检查创建形状、JSON、ID 一致及活表重复;registry 支持与墓碑禁复用由 editor/最终校验负责。 */ export function stageCreation(document:Document,change:Extract):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=change.kind==='element'?detachElementData(change.after as ElementRecord):freeze(clone(change.after)); // 只替换被追加的表,另一表/墓碑沿用;新 Document 标记为 draft,旧视图不随本次 added 改变。 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; } /** Materialize transaction-local append tables before publication. */ /** 只有已标记 draft 通过空 changes 物化成工厂 envelope/readMap;其它文档原样返回。 * 不在这里校验 schema/树,也不把任何外来 Document 自动视为已校验或深度不可变。 */ export const materializeCreations=(document:Document)=>drafts.has(document)?applyChanges(document,[]):document;