// 本体 IR 合成器:从后端 /api/meta/model/{code}(解析后的 YAML→JSON)在浏览器端合成统一 IR。 // 等价于 onto-app 里 Python codegen 的 IR 构建,改为 TypeScript 实现(“换一种方式重写”)。 // 仅读取现有只读接口,不改后端;三个视图(ER / 网络图 / 流程图)共用产物。 import { api } from '../api' import type { Ontology, OntoObject, OntoAttribute, OntoRelationship, OntoBehavior, OntoRule, OntoEvent, ProcessModel, ProcessNode, ProcessFlow, } from './types' const A = (x: any): T[] => (Array.isArray(x) ? x : []) const S = (x: any): string => (x == null ? '' : String(x)) /** 拉取相关本体层并合成 IR。 */ export async function loadOntology(): Promise { const [m1, m2, me, mr, m4] = await Promise.all([ api.model('M1'), api.model('M2'), api.model('ME'), api.model('MetaRule'), api.model('M4'), ]) return buildOntology(m1.content, m2.content, me.content, mr.content, m4.content) } export function buildOntology(m1: any, m2: any, me: any, mr: any, m4: any): Ontology { const objects = buildObjects(m1) const events = buildEvents(me) const rules = buildRules(mr) const behaviors = buildBehaviors(m2) linkRulesToBehaviors(behaviors, rules, m4) const processModels = buildProcessModels(m4) return { domain: '销售订单交易(M 系列本体)', version: '2.0', objects, behaviors, rules, events, navigations: [], // 本项目无显式导航树,网络图退化为按聚合平铺 processModels, } } // ============================================================ // M1 → objects(聚合根 + 组合子实体,含属性/主键/外键/引用/组合关系) // ============================================================ function buildObjects(m1: any): OntoObject[] { const out: OntoObject[] = [] for (const agg of A(m1?.aggregates)) { const aggregateId = S(agg.aggregateId) const root = agg.aggregateRoot || {} const rootId = S(root.name) || aggregateId const rootAttrs = mapAttributes(root.identifier, A(root.attributes)) const rootRels: OntoRelationship[] = [] // 跨聚合引用(refAggregate 字段)→ 引用关系 for (const a of rootAttrs) { if (a.ref) rootRels.push({ name: a.ref.root, label: a.name, target_object_id: a.ref.root, kind: 'MANY_TO_ONE', refType: 'reference', fk_field: a.name, }) } // 组合关系(relations[relationType=composition])→ 子实体 const childNames = A(root.relations).filter((r: any) => S(r.relationType) === 'composition').map((r: any) => S(r.ref)) for (const cn of childNames) rootRels.push({ name: cn, label: '组合', target_object_id: cn, kind: 'ONE_TO_MANY', refType: 'composition', }) out.push({ id: rootId, name: rootId, alias: aggregateId, description: S(root.description) || S(agg.description), is_aggregate_root: true, parent_id: null, aggregateId, attributes: rootAttrs, relationships: rootRels, }) // 子实体 for (const ent of A(agg.entities)) { const entId = S(ent.name) const entAttrs = mapAttributes(ent.localIdentifier, A(ent.attributes)) const entRels: OntoRelationship[] = [] for (const a of entAttrs) { if (a.ref) entRels.push({ name: a.ref.root, label: a.name, target_object_id: a.ref.root, kind: 'MANY_TO_ONE', refType: 'reference', fk_field: a.name, }) } out.push({ id: entId, name: entId, alias: `${aggregateId}.${entId}`, description: S(ent.description), is_aggregate_root: false, parent_id: rootId, aggregateId, attributes: entAttrs, relationships: entRels, }) } } return out } /** identifier(主键,M1 中单列于外层)前置为合成主键属性 + 其余字段。 */ function mapAttributes(identifier: any, attrs: any[]): OntoAttribute[] { const out: OntoAttribute[] = [] const idName = S(identifier) if (idName) out.push({ name: idName, label: idName, type: 'string', is_primary_key: true, required: true }) for (const a of attrs) { const ref = a.refAggregate ? { aggregate: S(a.refAggregate), root: S(a.refRoot), identifier: S(a.refIdentifier) } : undefined const cons = a.constraints || undefined const desc = S(a.description) out.push({ name: S(a.name), label: S(a.name), type: S(a.type) || 'string', is_foreign_key: !!ref, ref, constraints: cons, required: cons?.required || undefined, unique: cons?.unique || undefined, transient: /瞬态|不落库/.test(desc) || undefined, description: desc || undefined, }) } return out } // ============================================================ // M2 → behaviors(命令) // ============================================================ function buildBehaviors(m2: any): OntoBehavior[] { const out: OntoBehavior[] = [] for (const beh of A(m2?.behaviors)) { const owner = S(beh.aggregateRoot) for (const cmd of A(beh.commands)) { out.push({ id: S(cmd.cmdId), name: S(cmd.cmdId), owner_object_id: owner, type: 'COMMAND', commandType: S(cmd.commandType) || 'create', description: S(cmd.desc), applied_rules: [], produced_events: A(cmd.emitEvents).map(S), preconditions: A(cmd.validations).map(S), effects: A(cmd.derivations).map((d: any) => `${S(d.targetField)} = ${S(d.expression)}`) .concat(cmd.effect ? [`${S(cmd.effect.op)} ${S(cmd.effect.targetField)}`] : []), }) } } return out } // ============================================================ // MetaRule → rules;并按 bindScene→绑定命令,回填 behavior.applied_rules // ============================================================ function buildRules(mr: any): OntoRule[] { const out: OntoRule[] = [] for (const g of A(mr?.metaRuleModel?.ruleGroups)) { for (const r of A(g.rules)) { out.push({ id: S(r.ruleId), name: S(r.ruleName), type: S(r.effect), expression: S(r.matchCondition), enforced: true, bindScene: S(g.bindScene), description: S(r.message), }) } } return out } function linkRulesToBehaviors(behaviors: OntoBehavior[], rules: OntoRule[], m4: any) { const byId = new Map(behaviors.map((b) => [b.id, b])) const scenes = A(m4?.sceneModel?.sceneDefinitions) for (const rule of rules) { if (!rule.bindScene) continue const scene = scenes.find((s: any) => S(s.sceneId) === rule.bindScene) if (!scene) continue for (const cmdId of collectBindCommands(A(scene.flowSteps))) { const b = byId.get(cmdId) if (b && !b.applied_rules.includes(rule.id)) b.applied_rules.push(rule.id) } } } /** 递归收集 flowSteps 里所有 bindCommand 的命令 id(含 then/else/body/branches/cases/default 嵌套)。 */ function collectBindCommands(steps: any[]): string[] { const acc: string[] = [] const walk = (arr: any[]) => { for (const st of A(arr)) { if (S(st.stepType) === 'bindCommand' && st.bindCommand) acc.push(S(st.bindCommand)) walk(st.then); walk(st.else); walk(st.body); walk(st.default) for (const br of A(st.branches)) walk(br.steps) for (const c of A(st.cases)) walk(c.steps) } } walk(steps) return acc } // ============================================================ // ME → events // ============================================================ function buildEvents(me: any): OntoEvent[] { const out: OntoEvent[] = [] for (const def of A(me?.eventModel?.aggregateEventDefinitions)) { const aggregate = S(def.aggregateId) const events = def.events || {} for (const name of Object.keys(events)) { const e = events[name] || {} out.push({ id: name, name, aggregate, topic: S(e.topic), payload: A(e.payload).map(S), consumers: A(e.consumers).map(S), }) } } return out } // ============================================================ // M4 flowSteps → ProcessModel[](BPMN 语义骨架,供 bpmn-elk-layout 布局) // ============================================================ function buildProcessModels(m4: any): ProcessModel[] { const scenes = A(m4?.sceneModel?.sceneDefinitions) return scenes.map((sc: any) => synthProcess(sc)).filter((p: ProcessModel) => p.nodes.length > 1) } /** 单场景 flowSteps → 节点 + 序列流(递归处理控制流)。 */ function synthProcess(scene: any): ProcessModel { const nodes: ProcessNode[] = [] const flows: ProcessFlow[] = [] let c = 0 const nid = (p: string) => `${p}_${++c}` const push = (n: ProcessNode) => { nodes.push(n); return n.id } const connect = (froms: string[], to: string, opts?: Partial) => froms.forEach((f) => flows.push({ id: nid('Flow'), source: f, target: to, ...opts })) const start = push({ id: nid('Start'), type: 'startEvent', name: '开始' }) // build:把一段 steps 接到 incoming 之后,firstEdge 修饰 incoming→首节点的连线;返回“尾部”节点集合 const build = (steps: any[], incoming: string[], firstEdge?: Partial): string[] => { let tails = incoming let edge = firstEdge for (const st of A(steps)) { const r = node(st, tails, edge) tails = r edge = undefined // 仅第一段带修饰 } return tails } const node = (st: any, incoming: string[], firstEdge?: Partial): string[] => { const t = S(st.stepType) const simple = (type: ProcessNode['type'], name: string, extra?: Partial): string[] => { const id = push({ id: nid('Node'), type, name, ...extra }); connect(incoming, id, firstEdge); return [id] } switch (t) { case 'start': return incoming case 'return': case 'end': case 'errorEnd': { const id = push({ id: nid('End'), type: 'endEvent', name: t === 'errorEnd' ? '异常结束' : '结束' }) connect(incoming, id, firstEdge); return [] } case 'bindCommand': return simple('task', S(st.bindCommand), { behaviorId: S(st.bindCommand), lane: S(st.bindAggregate) }) case 'readOnlyCheck': return simple('task', `只读校验 ${S(st.bindAggregate)}`) case 'validate': return simple('task', '校验') case 'derive': return simple('task', '派生') case 'assign': { const label = A(st.assignments).length ? `赋值 ${A(st.assignments).map((x: any) => S(x.targetField)).join(', ')}` : `赋值 ${S(st.targetField)}` return simple('task', label) } case 'script': return simple('task', `脚本 → ${S(st.assignTo) || S(st.expression)}`) case 'transform': return simple('task', '转换') case 'callService': return simple('task', `调用服务 ${S(st.method)} ${S(st.endpoint)}`) case 'userTask': return simple('userTask', `人工任务 ${S(st.role) ? '· ' + S(st.role) : ''}`) case 'timer': return simple('intermediateCatchEvent', `定时 ${S(st.delayMs)}ms`) case 'waitEvent': return simple('intermediateCatchEvent', `等待事件 ${S(st.waitEvent)}`) case 'emitEvent': return simple('intermediateCatchEvent', `发事件 ${S(st.event)}`) case 'condition': { const g = push({ id: nid('Gw'), type: 'exclusiveGateway', name: '判断' }) connect(incoming, g, firstEdge) const thenT = build(A(st.then), [g], { name: '是', condition: S(st.when) }) const elseT = build(A(st.else), [g], { name: '否' }) return [...thenT, ...elseT] } case 'switch': { const g = push({ id: nid('Gw'), type: 'exclusiveGateway', name: `分派 ${S(st.switchOn)}` }) connect(incoming, g, firstEdge) const tails: string[] = [] for (const cs of A(st.cases)) tails.push(...build(A(cs.steps), [g], { name: `= ${S(cs.equals)}` })) tails.push(...build(A(st.default), [g], { name: 'default' })) return tails.length ? tails : [g] } case 'while': { const g = push({ id: nid('Gw'), type: 'exclusiveGateway', name: '循环判断' }) connect(incoming, g, firstEdge) const bodyT = build(A(st.body), [g], { name: '循环', condition: S(st.when) }) connect(bodyT, g) // 回边 return [g] // 退出边由后续步骤接出 } case 'parallel': { const split = push({ id: nid('Gw'), type: 'parallelGateway', name: '并行分叉' }) connect(incoming, split, firstEdge) const join = push({ id: nid('Gw'), type: 'parallelGateway', name: '并行汇合' }) for (const br of A(st.branches)) { const bt = build(A(br.steps), [split]) connect(bt, join) } return [join] } case 'retry': { const id = push({ id: nid('Node'), type: 'task', name: `重试 ×${S(st.maxAttempts)}` }) connect(incoming, id, firstEdge) return build(A(st.body), [id]) } case 'compensate': return simple('task', `补偿 ${S(st.compensateCommand)}`) default: return simple('task', t || '步骤') } } const tails = build(A(scene.flowSteps), [start]) if (tails.length) { const end = push({ id: nid('End'), type: 'endEvent', name: '结束' }); connect(tails, end) } return { id: S(scene.sceneId), name: S(scene.sceneName) || S(scene.sceneId), nodes, flows } }