build.ts
13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// 本体 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 = <T = any>(x: any): T[] => (Array.isArray(x) ? x : [])
const S = (x: any): string => (x == null ? '' : String(x))
/** 拉取相关本体层并合成 IR。 */
export async function loadOntology(): Promise<Ontology> {
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<ProcessFlow>) =>
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<ProcessFlow>): 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<ProcessFlow>): string[] => {
const t = S(st.stepType)
const simple = (type: ProcessNode['type'], name: string, extra?: Partial<ProcessNode>): 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 }
}