Skip to main content

ice-entity-designer-dsl · 领域建模 JSON DSL(当前 v0.0.29)

🔌 DSL 层 · 应用级 · 7 种 kind 的 JSON 文档驱动设计器(当前 v0.0.29)

一份 JSON 文档同时驱动渲染、校验与导出三种能力;七种 kind(ER / flowchart / BPMN / UML / statechart / gantt / power)共用同一套图层自动布局layeredLayout),并通过 toDsl() 实现往返写回——用户改过的画布能原样导出成同一份 DSL,再渲染回去。

这是 ice-entity-designer 之上的一层 JSON-first DSL:AI Agent 不需要学习命令式的画布 API(new EntityDesigner(...)createEntitycreateRelation……),只要产出结构化 JSON,同一份文档既能直出设计器实例,也能在服务端做校验与序列化。它和 ice-render 通用 DSL(ice-render-dsl)是同一套「数据 → 引擎」哲学,但词汇表不互通——领域建模用本包,通用图形用 ice-render-dsl

MIT License · 作者:大漠穷秋(damoqiongqiu@126.com

1. 它是什么

ice-entity-designer-dsl一份 JSON 文档覆盖 7 种图 kind。整张文档是「节点 + 连线」(ER 文档用 entities / relations 的同源写法)的平坦描述,坐标大多可省略——缺省时由共享的分层自动布局推出来:

  • ERentities / relations):实体-关系模型与数据库 Schema。
  • flowchart:流程图、决策树、算法,可选坐标(分层自动布局)。
  • BPMN:业务流,含池 / 泳道(真容器)、事件 / 网关 / 任务角标 / 数据对象 / 注释,可导出 BPMN 2.0 XML。
  • UML:类图(类 / 接口 / 枚举 + 自由文本成员),含六种关系,可互操作 PlantUML / Mermaid。
  • statechart:状态机(伪状态 / 状态 / 复合状态容器 + 转移标签 事件[守卫]/动作)。
  • gantt:排期(任务含 start / days / progress,连线是完成→开始依赖),完全无坐标,横轴时间、纵轴声明顺序。
  • power:电力一次系统图(设备符号与 ice-entity-designer 的电力包对齐,attachedTo 表达母线 T 接、voltageLevel 驱动色标)。

2. 安装与 UMD 加载顺序

# DSL 运行时会顺带安装 ice-entity-designer(它经由设计器渲染)
npm install ice-entity-designer-dsl ice-entity-designer ice-render

引擎(ice-render)是两者的 peer 依赖,在 UMD 产物里保持外部引用globals: { 'ice-render': 'ICE' } / { 'ice-entity-designer': 'IED' })。所以一个纯 <script> 页面必须按下面顺序加载三个 UMD:

<canvas id="canvas" width="1200" height="800"></canvas>

<script src="node_modules/ice-render/dist/index.umd.js"></script>
<script src="node_modules/react/umd/react.production.min.js"></script>
<script src="node_modules/react-dom/umd/react-dom.production.min.js"></script>
<script src="node_modules/dayjs/dayjs.min.js"></script>
<script src="node_modules/antd/dist/antd.min.js"></script>
<script src="node_modules/ice-entity-designer/dist/index.umd.js"></script>
<script src="node_modules/ice-entity-designer-dsl/dist/index.umd.js"></script>
<script>
const dsl = {
schemaVersion: 1,
layout: 'layered',
entities: [
{ id: 'customer', name: 'Customer', fields: [{ name: 'id', type: 'number', primary: true }] },
{ id: 'order', name: 'Order', fields: [{ name: 'id', type: 'number', primary: true }] },
],
relations: [{ source: 'customer', target: 'order', type: 'one-to-many' }],
};
const { ice, designer } = ICEDSL.renderDsl('canvas', dsl); // 全局命名空间 ICEDSL
</script>

加载顺序铁律:ice-render → react / react-dom / dayjs / antd → ice-entity-designer → ice-entity-designer-dsl。设计器依赖 React 生态与引擎全局 ICE,DSL 又依赖设计器全局 IED;顺序错则运行时报依赖缺失。

Node / ESM 工程里直接 import 即可,无需关心 UMD 顺序:

import {
validateDsl, compileDsl, compileFlowDsl, renderDsl,
} from 'ice-entity-designer-dsl';

3. 七种 kind 速览

kind用途典型场景
(缺省,ER)实体-关系模型数据库 Schema 设计、ORM 落地
flowchart流程 / 判定算法步骤、审批流、决策树
bpmn业务流程建模跨部门协作流程、可互操作 BPMN 2.0
umlUML 类图类关系梳理、接口契约
statechart有限状态机订单状态、协议状态流转
gantt项目排期研发计划、关键路径分析
power电力一次系统图变电站主接线、电压色标与五防

各 kind 的词汇表不互通:ER 用 entities / relations,其余六种统一用 nodes / edges。Agent 每次只选一种 kind,不要混写字段。

4. DSL 文档形状(以 ER 为例)

根节点只含这五个字段(ER 文档例外:无 kind,用 entities / relations):

字段说明
schemaVersionDSL 版本号(当前 1,对外常量 DSL_SCHEMA_VERSION
entities实体数组,每项含 id / name / fields[];字段含 type 与约束(primary / foreignKey / unique / nullable / autoIncrement / default / length / comment / index
relations关系数组,type 取 one-to-many / many-to-one / one-to-one / many-to-many;many-to-many 必须带 joinTableName
layout自动布局:grid / layered / horizontal / vertical;不填则用 options.gapX/gapY
optionsfitViewportfitViewportPaddingrouteTypegapXgapY

下面这份「电商模型」是一份真实可渲染、可序列化的 DSL 文档(不是截图):

ice-entity-designer-dsl 文档(AI Agent 可直接产出)
{
"schemaVersion": 1,
"layout": "layered",
"entities": [
{
"id": "customer",
"name": "Customer",
"fields": [
{ "name": "id", "type": "number", "primary": true, "autoIncrement": true },
{ "name": "email", "type": "string", "unique": true, "nullable": false },
{ "name": "name", "type": "string", "nullable": false }
]
},
{
"id": "order",
"name": "Order",
"fields": [
{ "name": "id", "type": "number", "primary": true, "autoIncrement": true },
{ "name": "customerId", "type": "number", "foreignKey": true, "nullable": false },
{ "name": "status", "type": "string", "default": "pending" },
{ "name": "total", "type": "decimal", "length": "12,2" }
]
},
{
"id": "product",
"name": "Product",
"fields": [
{ "name": "id", "type": "number", "primary": true, "autoIncrement": true },
{ "name": "sku", "type": "string", "unique": true, "nullable": false },
{ "name": "price", "type": "decimal", "length": "10,2" }
]
}
],
"relations": [
{ "source": "customer", "target": "order", "type": "one-to-many",
"sourceField": "id", "targetField": "customerId",
"sourceCardinality": "1", "targetCardinality": "0..N",
"label": "places", "onDelete": "CASCADE" }
],
"options": { "fitViewport": true, "fitViewportPadding": 48, "routeType": "orthogonal", "gapX": 120, "gapY": 60 }
}

5. 校验与 IED_DSL_* 诊断码

validateDsl(dsl)kind 分发到对应校验器,并返回 { valid, errors, diagnostics }

  • errors: string[]——给人读的英文句子(保留原样,兼容既有调用方)。
  • diagnostics: { severity, code, message, path }[]——给机器读的,code 才是合同

契约见 ice-render docs/architecture/17-i18n-boundary.md:库不翻译文案,但必须给稳定 id。Agent / 工具请按 code 分支、按 path 定位path 形如 nodes[3].source),不要匹配 message 里的自然语言

单 kind 校验器:validateFlowDsl / validateBpmnDsl / validateUmlDsl / validateStatechartDsl / validateGanttDsl / validatePowerDsl

完整 IED_DSL_* 诊断码(定义见 src/types.tsIED_DSL_CODES):

诊断码含义
IED_DSL_ROOT_NOT_OBJECT根节点不是对象
IED_DSL_SCHEMA_VERSION_UNSUPPORTEDschemaVersion 不受支持
IED_DSL_NOT_OBJECT节点 / 连线本身不是对象
IED_DSL_ID_INVALIDid 缺失或不是非空字符串
IED_DSL_ID_DUPLICATEDid 重复
IED_DSL_NODES_NOT_ARRAYnodes 不是数组
IED_DSL_EDGES_NOT_ARRAYedges 不是数组
IED_DSL_ENTITIES_NOT_ARRAYentities 不是数组
IED_DSL_RELATIONS_NOT_ARRAYrelations 不是数组
IED_DSL_FIELDS_NOT_ARRAYfields 不是数组
IED_DSL_KIND_INVALIDkind / type 不在允许集合里
IED_DSL_FIELD_TYPE字段类型不对(应为字符串 / 数字 / 字符串数组)
IED_DSL_FIELD_ENUM字段取值不在枚举集合里
IED_DSL_FIELD_FORMAT字段格式不对(日期、电压等级等)
IED_DSL_FIELD_RANGE字段取值越界(行号 / 天数 / 进度)
IED_DSL_FIELD_NAME_INVALID字段名(如 fields[i].name)非法
IED_DSL_EDGE_ENDPOINT_INVALID连线端点不是非空字符串
IED_DSL_EDGE_ENDPOINT_UNKNOWN连线端点指向不存在的节点
IED_DSL_EDGE_SELF_LOOP连线自环
IED_DSL_PARENT_INVALIDparent 不是非空字符串
IED_DSL_PARENT_UNKNOWNparent 指向不存在的节点
IED_DSL_PARENT_SELFparent 指向自己
IED_DSL_PARENT_KINDparent 指向不允许的节点类型(池 / 泳道 / 复合状态…)
IED_DSL_PARENT_NOT_ALLOWED该节点不允许有 parent(如池)
IED_DSL_ATTACHED_TO_UNKNOWNattachedTo 指向不存在的节点
IED_DSL_ATTACHED_TO_NOT_BUSBARattachedTo 指向的不是母线
import { validateDsl } from 'ice-entity-designer-dsl';

const { valid, diagnostics } = validateDsl(dsl);
if (!valid) {
for (const d of diagnostics) {
// 按 code 分支纠错、按 path 定位
console.log(d.code, d.path, d.message);
}
}

6. 编译与渲染

编译层把 DSL 翻成组件 props,每种 kind 一个 compile*Dsl;它们共享同一套 layeredLayout(参数 direction: "vertical" | "horizontal"),只在「层如何分层、容器内如何落位」上有各自的小规则:

编译函数产出布局特点
compileDslER:Entity / Relation propslayout 驱动
compileFlowDslFlowNode / FlowEdge props分层自动布局(层 0 = 无入边节点)
compileBpmnDslFlowNode / FlowEdge props容器自动算几何 + 容器内自动落位
compileUmlDslUmlClass / UmlRelation props继承关系驱动的上下分层
compileStatechartDslStateNode / StateTransition props流向自左而右 + 复合状态自动算尺寸
compileGanttDslGanttTask / GanttDependency props无坐标,行号自动填
compilePowerDslPowerSymbol / PowerLine props母线 T 接解析 + 电压色标应用

渲染层 renderDsl(canvasOrId, dsl) 返回 { kind, ice, designer }kind 字段让你判别拿到的是哪个设计器实例。各 kind 也有对应的 render*Dsl(如 renderFlowDsl / renderBpmnDsl / renderUmlDsl / renderStatechartDsl / renderGanttDsl / renderPowerDsl),两者都支持浏览器(UMD 全局 ICEDSL)与 Node(ESM import)。

import { renderDsl } from 'ice-entity-designer-dsl';

const result = renderDsl('canvas', flowchart);
// result.kind === 'flowchart',result.designer 是 FlowDesigner(createNode / createEdge / undo …)

设计器实例上还有语义校验方法,按 kind 各自存在:validatePower() / validateBpmn() / validateGantt() / validateUml() / validateStatechart()(ER 与流程图沿用 ice-entity-designer 既有的 validate())。

7. toDsl 往返

renderDsl() 返回的 result 自带 toDsl(),把用户改过的实例写回成同一份 DSL 文档。单独使用时也可调用 toDsl(resultOrDesigner, { kind? })(不传 kind 时按图元自动判别):

const result = ICEDSL.renderDsl('canvas', doc);

// 用户在画布上拖动 / 改名 / 分合 / 增删……
result.designer.updateNode('check', { title: '库存够吗?', left: 500, top: 480 });

const edited = result.toDsl(); // 同一份 DSL:位置与语义改动都在
ICEDSL.validateDsl(edited).valid; // true —— 导出结果永远能过校验
ICEDSL.renderDsl('canvas', edited); // 也能直接再渲染一遍

往返契约(7 种 kind 都有测试兜底):

  • id 保真:Agent 产出的 id 原样带回(导出顺序 = 实例顺序,BPMN 会把池 / 泳道排在业务图元之前);文档里没写 id 的连线会拿到 edge-0 这类稳定 id,而非每次随机 UUID。
  • 坐标是绝对坐标:嵌套容器(BPMN 池 / 泳道、状态机复合状态)的相对坐标已沿父链还原。
  • 只导出 DSL 词汇表里的字段:引擎内部字段(zIndex、变换矩阵等)不外泄。
  • 必过校验、必须可序列化:结果无 undefinedJSON.parse(JSON.stringify(doc)) 无损往返。
  • 再渲染等价:导出文档再渲染一遍,节点 id 与位置与上遍一致。
  • 推断不出来就报错:实例里没有任何可识别图元时,toDsl() 抛错并提示显式传 kind,不会悄悄当成 ER 文档。

8. 导出

渲染结果的 designer 提供矢量导出,SVG 由组件树 + 路径命令重新生成,与画布同一口径(几何、样式、不透明度、阴影、连线标签),可被任意外部工具栅格化成 PNG / PDF:

  • SVGresult.designer.toSvg(options)(flowchart / BPMN 等通用矢量导出)。options = { area: 'content' | 'viewport', padding, scale, background, includeTools }
  • BPMN 2.0 XML:先用 result.designer.validateBpmn() 做语义检查,再用 ice-entity-designertoBpmnXml(result.designer) 导出(含 BPMNDI,互操作格式、非执行模型);fromBpmnXml(xml, result.designer) 反向导入。
  • PlantUMLice-entity-designertoPlantUml(result.designer)(UML 类图)/ toPlantUmlState(result.designer)(状态图)导出文本;fromPlantUml(text, result.designer) / fromPlantUmlState(text, result.designer) 反向导入。

甘特图还可 result.designer.autoSchedule()(按依赖把排期推到最早可行)与 result.designer.criticalPath()(标准 CPM 算关键路径),再 toSvg() 导出含日期刻度与进度的矢量图。

9. Agent 发现

本包为 AI Agent 提供四路接入,核心运行时不依赖 MCP

  1. npm 包导出——validateDsl / compile*Dsl / renderDsl / toDsl 等(见上文)。
  2. AGENTS.md——本仓库根目录的 Agent 指南:铁律「main 只做集成与发布、开发在临时分支或 dev」、必须含 schemaVersion: 1、每次只选一种 kind、实体 id 唯一、关系端点引用已存在的实体、layered 用于依赖型图、grid 用于简单表格。
  3. skills/ice-entity-designer-dsl/SKILL.md——可被 Agent 运行时加载的技能说明。
  4. 可选 MCP 包装——独立包提供,按需接入。

最稳的接入形态:让 Agent 产出 JSON DSL → 用 validateDsl 自检(按 code / path 纠错)→ renderDsl 直出设计器 → 用户编辑后 toDsl() 写回。全程数据驱动,引擎负责渲染与序列化。

一个完整的实时例子(就是仓库里的 examples/entity-editor-dsl.html

相关链接