// 结构化编辑会话:每个模型层持有一份 yaml Document(保留注释),编辑走「按路径改节点」, // 只动被编辑的节点、其余原文(含注释/顺序)不变。保存时把脏层逐个 PUT 回后端。 import { parseDocument, type Document } from 'yaml' import { api } from '../api' export type Path = (string | number)[] export class EditSession { private docs = new Map() dirty = new Set() /** 懒加载某层的 Document(用后端返回的源文件原文,保留注释)。 */ async ensure(code: string): Promise { if (!this.docs.has(code)) { const d = await api.model(code) this.docs.set(code, parseDocument(d.raw ?? '')) } return this.docs.get(code)! } has(code: string) { return this.docs.has(code) } toJS(code: string): any { return this.docs.get(code)?.toJS() } setIn(code: string, path: Path, value: any) { const doc = this.docs.get(code); if (!doc) return doc.setIn(path, value) this.dirty.add(code) } deleteIn(code: string, path: Path) { const doc = this.docs.get(code); if (!doc) return doc.deleteIn(path) this.dirty.add(code) } /** 在某数组末尾追加一个节点。 */ addIn(code: string, arrayPath: Path, value: any) { const doc = this.docs.get(code); if (!doc) return doc.addIn(arrayPath, doc.createNode(value)) this.dirty.add(code) } /** 设置一个约束键;value 为空/假时删除该键,并在约束对象空了时删除整个 constraints。 */ setConstraint(code: string, attrPath: Path, key: string, value: any) { const doc = this.docs.get(code); if (!doc) return const consPath = [...attrPath, 'constraints'] if (value === undefined || value === null || value === '' || value === false) { doc.deleteIn([...consPath, key]) const cons = doc.getIn(consPath) as any if (cons && typeof cons.toJSON === 'function') { const j = cons.toJSON() if (!j || Object.keys(j).length === 0) doc.deleteIn(consPath) } } else { if (doc.getIn(consPath) === undefined) doc.setIn(consPath, doc.createNode({})) doc.setIn([...consPath, key], value) } this.dirty.add(code) } // lineWidth:0 关闭自动折行,避免把未编辑的长字符串(如 description)重新折行造成无谓 diff。 render(code: string): string { return this.docs.get(code)?.toString({ lineWidth: 0 }) ?? '' } async saveAll(principal: string): Promise { const saved: string[] = [] for (const code of [...this.dirty]) { await api.saveModel(code, this.render(code), principal) saved.push(code) } this.dirty.clear() return saved } reset() { this.docs.clear(); this.dirty.clear() } }