Files
Endoleap/src/lib/canvas-runtime/import-identities.ts
T

46 lines
3.3 KiB
TypeScript

/**
* 一个导入runner的稳定source key→新ID分配器,复用持久job.stableIds而非重开重新随机分配。
* 使用copy-on-write避免每条无新依赖的记录都复制完整映射;本模块只检查本映射内重复,项目全局身份由上层校验。
*/
import {dataRecord} from '../canvas-core/data-containers.ts';
import type {Id} from '../canvas-core/types.ts';
import {fault} from '../canvas-core/values.ts';
/** 源记录/依赖键到目标ID的只读字典,element/canvas前缀由调用者约定。 */
type Ids=Readonly<Record<string,Id>>;
/** One runner's last immutable ID index. Copies are deferred until allocation. */
/** 一个 runner 的捕获工厂,缓存最近一张可复用表及其 used Set;每次 capture 创建独立分配上下文。
* 初始表的值域/空 ID/已有重复映射未在此完整校验,Set 只用于拦截后续 createId 与已知 ID 碰撞。
* 不查项目文档、墓碑、其它 runner 或服务身份空间;稳定映射须由 job 校验与发布层共同保证。 */
export function createImportIdentities(){
let cached:{ids:Ids;used:Set<Id>}|undefined;
/** 同引用命中缓存时直接复用;否则仅将冻结且描述符为字符串数据值的输入视为可复用。
* 这是性能准入,不是完整 schema/普通对象证明;其余输入以 Object.assign 捕获到无原型字典,getter 会在此求值。
* 首次分配前共享基表/used;写时再分离,避免每条只查既有键的记录重扫整张映射。 */
const capture=(input:Ids,createId:()=>Id)=>{
let identity=cached?.ids===input?cached:undefined;
if(!identity){
const reusable=Object.isFrozen(input)&&Object.values(Object.getOwnPropertyDescriptors(input)).every(property=>Object.hasOwn(property,'value')&&typeof property.value==='string');
const ids=reusable?input:Object.assign(Object.create(null),input),used=new Set<Id>(Object.values(ids));identity={ids,used};if(reusable)cached=identity;
}
let ids=identity.ids,used=identity.used,owned=ids!==input;
return Object.freeze({
/** 已有键返回原ID;首次遇到依赖才分配并检查本任务冲突,第一次写入时才复制映射/Set。 */
reserve(key:string){
if(typeof key!=='string'||!key)throw fault('invalid_import_key');
if(Object.hasOwn(ids,key))return ids[key];
const id=createId();if(typeof id!=='string'||!id||used.has(id))throw fault('duplicate_import_identity');
if(!owned){ids=Object.assign(Object.create(null),ids);used=new Set(used);owned=true;}
(ids as Record<string,Id>)[key]=id;used.add(id);return id;
},
/** 判断目标ID是否已被本映射使用,不声称该ID已加入文档或已同步。 */
has:(id:Id)=>used.has(id),
/** owned 缓冲通过 dataRecord 再捕获不可变值,后续 reserve 不改变已经返回的结果。
* 可复用输入且无分配时沿用原表;可变/getter 输入即使未分配也返回捕获表,每次 finish 可重新捕获。
* 返回不代表映射已落盘;runner 须与导入进度/文档同次发布和持久化。 */
finish:():Ids=>owned?dataRecord(ids):ids,
});
};
/** clear 只丢工厂最近索引;现有 capture 上下文仍能使用,不清已发布/持久 job.stableIds,也不回收 ID。 */
return Object.freeze(Object.assign(capture,{clear(){cached=undefined;}}));
}