ice-entity-designer-dsl 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 大漠穷秋
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,14 +1,20 @@
1
1
  # ice-entity-designer-dsl
2
2
 
3
- JSON-first ER DSL for AI agents to drive `ice-entity-designer` without learning the imperative canvas API.
3
+ JSON-first DSL for AI agents to drive `ice-entity-designer` without learning the imperative canvas API.
4
+
5
+ One document kind, two shapes:
6
+
7
+ - **ER document** (`entities` / `relations`): entity-relation models and database schemas.
8
+ - **Flowchart document** (`kind: "flowchart"` with `nodes` / `edges`): process flows,
9
+ decision trees, and algorithms, with optional coordinates (layered auto-layout).
4
10
 
5
11
  The package contains:
6
12
 
7
- - DSL types and schema
13
+ - DSL types and schema (ER + flowchart)
8
14
  - structural validator
9
- - compiler from DSL to Entity/Relation props
15
+ - compilers from DSL to Entity/Relation or FlowNode/FlowEdge props
10
16
  - browser runtime that renders DSL through `ice-entity-designer`
11
- - browser example with a JSON editor
17
+ - browser examples with a JSON editor (`examples/entity-editor-dsl.html`, `examples/flowchart-dsl.html`)
12
18
 
13
19
  ## Install
14
20
 
@@ -44,19 +50,51 @@ npm run build
44
50
  ## Node/ESM usage
45
51
 
46
52
  ```ts
47
- import { validateDsl, compileDsl, renderDsl } from 'ice-render-dsl';
53
+ import { validateDsl, compileDsl, compileFlowDsl, renderDsl } from 'ice-entity-designer-dsl';
48
54
  ```
49
55
 
56
+ ## Flowchart usage
57
+
58
+ Node coordinates are optional: if any node omits `left`/`top`, the whole graph is laid
59
+ out in layers (layer 0 = nodes without incoming edges; every other node = max
60
+ predecessor layer + 1).
61
+
62
+ ```js
63
+ const flowchart = {
64
+ schemaVersion: 1,
65
+ kind: 'flowchart',
66
+ nodes: [
67
+ { id: 'start', kind: 'terminator', title: '开始' },
68
+ { id: 'check', kind: 'decision', title: '库存充足?' },
69
+ { id: 'done', kind: 'terminator', title: '结束' },
70
+ { id: 'restock', kind: 'io', title: '通知补货' },
71
+ ],
72
+ edges: [
73
+ { source: 'start', target: 'check' },
74
+ { source: 'check', target: 'done', label: '是' },
75
+ { source: 'check', target: 'restock', label: '否', sourcePort: 'R', targetPort: 'L' },
76
+ ],
77
+ };
78
+
79
+ // kind === 'flowchart' 时 result.designer 是 FlowDesigner(createNode / createEdge / undo / …)
80
+ const result = ICEDSL.renderDsl('canvas', flowchart);
81
+ ```
82
+
83
+ Node kinds: `terminator` (start/end pill) / `process` (action, default) / `decision`
84
+ (diamond) / `io` (parallelogram). Edge ports are `T` / `R` / `B` / `L` / `C` (default
85
+ `B` → `T`); `linkShape` is `visio` (orthogonal, default) or `bezier`.
86
+
50
87
  ## API
51
88
 
52
- - `validateDsl(dsl)`
53
- - `compileDsl(dsl)`
54
- - `renderDsl(canvasOrId, dsl)`
89
+ - `validateDsl(dsl)` —— ER / flowchart documents (dispatches on `kind`); `validateFlowDsl(dsl)` for flowcharts only
90
+ - `compileDsl(dsl)` —— ER document → `Entity` / `Relation` props
91
+ - `compileFlowDsl(dsl)` —— flowchart document → `FlowNode` / `FlowEdge` props (with layered auto-layout)
92
+ - `renderDsl(canvasOrId, dsl)` —— renders either kind, returns `{ kind, ice, designer }`
55
93
  - `DSL_SCHEMA_VERSION`
56
94
 
57
95
  ## Example
58
96
 
59
- Open `examples/entity-editor-dsl.html` after building.
97
+ Open `examples/entity-editor-dsl.html` (ER) or `examples/flowchart-dsl.html` (flowchart) after building.
60
98
 
61
99
  ## Agent discovery
62
100
 
@@ -64,7 +102,7 @@ Agents can use this project through:
64
102
 
65
103
  1. npm package exports
66
104
  2. `AGENTS.md`
67
- 3. `skills/ice-render-dsl/SKILL.md`
105
+ 3. `skills/ice-entity-designer-dsl/SKILL.md`
68
106
  4. optional MCP wrapper in a separate package
69
107
 
70
108
  The core runtime does not require MCP.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("ice-entity-designer");function validateDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};if(void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion),Array.isArray(e.entities)){const i=new Set;e.entities.forEach((e,r)=>{const n=`entities[${r}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?i.has(e.id)?t.push(`${n}.id is duplicated: ${e.id}`):i.add(e.id):t.push(n+".id must be a non-empty string"),Array.isArray(e.fields)?e.fields.forEach((e,i)=>{e&&"object"==typeof e&&"string"==typeof e.name&&e.name.trim()||t.push(`${n}.fields[${i}].name must be a non-empty string`)}):t.push(n+".fields must be an array")):t.push(n+" must be an object")})}else t.push("entities must be an array");return void 0===e.relations||Array.isArray(e.relations)?(e.relations||[]).forEach((e,i)=>{const r=`relations[${i}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()||t.push(r+".source must be a non-empty string"),"string"==typeof e.target&&e.target.trim()||t.push(r+".target must be a non-empty string")):t.push(r+" must be an object")}):t.push("relations must be an array"),{valid:0===t.length,errors:t}}function entityName(e){return e.entityName||e.name||e.id}function relationType(e){return e.relationType||e.type||"one-to-many"}function compileDsl(e){return{entities:e.entities.map(e=>({id:e.id,entityName:entityName(e),fields:e.fields||[],left:e.left,top:e.top,width:e.width,height:e.height,style:e.style,headerStyle:e.headerStyle,fieldStyle:e.fieldStyle,dividerStyle:e.dividerStyle,draggable:e.draggable,interactive:e.interactive})),relations:(e.relations||[]).map((e,t)=>({id:e.id||"relation-"+t,relationType:relationType(e),sourceId:e.source,targetId:e.target,sourceField:e.sourceField||"id",targetField:e.targetField||"id",nullable:e.nullable,onDelete:e.onDelete,onUpdate:e.onUpdate,joinTableName:e.joinTableName,fromKey:e.fromKey,toKey:e.toKey,sourceCardinality:e.sourceCardinality,targetCardinality:e.targetCardinality,label:e.label,style:e.style,arrow:e.arrow,linkShape:e.linkShape,routeType:e.routeType,routeOffset:e.routeOffset,curveType:e.curveType,lineDash:e.lineDash})),layout:e.layout,options:e.options}}exports.DSL_SCHEMA_VERSION=1,exports.compileDsl=compileDsl,exports.renderDsl=function renderDsl(t,i){const r=validateDsl(i);if(!r.valid)throw new Error(r.errors.join("\n"));const n=compileDsl(i),a=(new e.ICE).init(t),o=new e.EntityDesigner(a);if(n.entities.forEach(e=>{o.createEntity(e)}),function positionLinks(e,t){const i=new Map;return e.entities.forEach(e=>{i.set(e.state.id,e.getMinBoundingBox(!0))}),t.relations.map(e=>{const t=i.get(e.sourceId),r=i.get(e.targetId);let n="R",a="L";if(t&&r){const e=r.center[0]-t.center[0],i=r.center[1]-t.center[1];Math.abs(e)>Math.abs(i)?(n=e>=0?"R":"L",a=e>=0?"L":"R"):(n=i>=0?"B":"T",a=i>=0?"T":"B")}return{...e,routeType:e.routeType||"orthogonal",linkShape:e.linkShape||"visio",links:{start:{id:e.sourceId,position:n},end:{id:e.targetId,position:a}}}})}(o,n).forEach(e=>{o.createRelation(e)}),"layered"===n.layout||"horizontal"===n.layout){const t=n.options||{};new e.ICELayeredLayout({gapX:t.gapX||120,gapY:t.gapY||50}).layoutContainer(a)}return{ice:a,designer:o}},exports.validateDsl=validateDsl;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("ice-entity-designer");function isFlowDsl(e){return!!e&&"object"==typeof e&&"flowchart"===e.kind}const t=["terminator","process","decision","io"],i=["T","R","B","L","C"];function validateDsl(e){return isFlowDsl(e)?validateFlowDsl(e):function validateErDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion);if(Array.isArray(e.entities)){const i=new Set;e.entities.forEach((e,o)=>{const r=`entities[${o}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?i.has(e.id)?t.push(`${r}.id is duplicated: ${e.id}`):i.add(e.id):t.push(r+".id must be a non-empty string"),Array.isArray(e.fields)?e.fields.forEach((e,i)=>{e&&"object"==typeof e&&"string"==typeof e.name&&e.name.trim()||t.push(`${r}.fields[${i}].name must be a non-empty string`)}):t.push(r+".fields must be an array")):t.push(r+" must be an object")})}else t.push("entities must be an array");void 0===e.relations||Array.isArray(e.relations)?(e.relations||[]).forEach((e,i)=>{const o=`relations[${i}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()||t.push(o+".source must be a non-empty string"),"string"==typeof e.target&&e.target.trim()||t.push(o+".target must be a non-empty string")):t.push(o+" must be an object")}):t.push("relations must be an array");return{valid:0===t.length,errors:t}}(e)}function validateFlowDsl(e){const o=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};void 0!==e.schemaVersion&&1!==e.schemaVersion&&o.push("Unsupported schemaVersion: "+e.schemaVersion);const r=new Set;return Array.isArray(e.nodes)?e.nodes.forEach((e,i)=>{const s=`nodes[${i}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?r.has(e.id)?o.push(`${s}.id is duplicated: ${e.id}`):r.add(e.id):o.push(s+".id must be a non-empty string"),void 0!==e.kind&&-1===t.indexOf(e.kind)&&o.push(`${s}.kind must be one of ${t.join("/")}`),["left","top","width","height"].forEach(t=>{const i=e[t];void 0!==i&&"number"!=typeof i&&o.push(`${s}.${t} must be a number when present`)})):o.push(s+" must be an object")}):o.push("nodes must be an array"),void 0===e.edges||Array.isArray(e.edges)?(e.edges||[]).forEach((e,t)=>{const s=`edges[${t}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()&&r.has(e.source)||o.push(s+".source must reference an existing node id"),"string"==typeof e.target&&e.target.trim()&&r.has(e.target)||o.push(s+".target must reference an existing node id"),void 0!==e.label&&"string"!=typeof e.label&&o.push(s+".label must be a string when present"),void 0!==e.sourcePort&&-1===i.indexOf(e.sourcePort)&&o.push(`${s}.sourcePort must be one of ${i.join("/")}`),void 0!==e.targetPort&&-1===i.indexOf(e.targetPort)&&o.push(`${s}.targetPort must be one of ${i.join("/")}`),void 0!==e.linkShape&&"visio"!==e.linkShape&&"bezier"!==e.linkShape&&o.push(s+'.linkShape must be "visio" or "bezier" when present')):o.push(s+" must be an object")}):o.push("edges must be an array when present"),{valid:0===o.length,errors:o}}function entityName(e){return e.entityName||e.name||e.id}function relationType(e){return e.relationType||e.type||"one-to-many"}function compileDsl(e){if(isFlowDsl(e))throw new Error("compileDsl() 只处理 ER 文档;流程图文档请使用 compileFlowDsl()");if(!e||!Array.isArray(e.entities))throw new Error("ER 文档必须包含 entities 数组");return{entities:e.entities.map(e=>({id:e.id,entityName:entityName(e),fields:e.fields||[],left:e.left,top:e.top,width:e.width,height:e.height,style:e.style,headerStyle:e.headerStyle,fieldStyle:e.fieldStyle,dividerStyle:e.dividerStyle,draggable:e.draggable,interactive:e.interactive})),relations:(e.relations||[]).map((e,t)=>({id:e.id||"relation-"+t,relationType:relationType(e),sourceId:e.source,targetId:e.target,sourceField:e.sourceField||"id",targetField:e.targetField||"id",nullable:e.nullable,onDelete:e.onDelete,onUpdate:e.onUpdate,joinTableName:e.joinTableName,fromKey:e.fromKey,toKey:e.toKey,sourceCardinality:e.sourceCardinality,targetCardinality:e.targetCardinality,label:e.label,style:e.style,arrow:e.arrow,linkShape:e.linkShape,routeType:e.routeType,routeOffset:e.routeOffset,curveType:e.curveType,lineDash:e.lineDash})),layout:e.layout,options:e.options}}const o={terminator:{width:e.FLOW_NODE_KINDS.terminator.width,height:e.FLOW_NODE_KINDS.terminator.height,fill:e.FLOW_NODE_KINDS.terminator.fill,stroke:e.FLOW_NODE_KINDS.terminator.stroke},process:{width:e.FLOW_NODE_KINDS.process.width,height:e.FLOW_NODE_KINDS.process.height,fill:e.FLOW_NODE_KINDS.process.fill,stroke:e.FLOW_NODE_KINDS.process.stroke},decision:{width:e.FLOW_NODE_KINDS.decision.width,height:e.FLOW_NODE_KINDS.decision.height,fill:e.FLOW_NODE_KINDS.decision.fill,stroke:e.FLOW_NODE_KINDS.decision.stroke},io:{width:e.FLOW_NODE_KINDS.io.width,height:e.FLOW_NODE_KINDS.io.height,fill:e.FLOW_NODE_KINDS.io.fill,stroke:e.FLOW_NODE_KINDS.io.stroke}};function compileFlowDsl(e){const t=e.options||{},i=(e.nodes||[]).map(e=>{const t=function nodeKind(e){return e.kind&&o[e.kind]?e.kind:"process"}(e),i=o[t];return{id:e.id,typeId:"FlowNode",kind:t,title:(r=e,r.title||r.name||r.id),left:"number"==typeof e.left?e.left:Number.NaN,top:"number"==typeof e.top?e.top:Number.NaN,width:"number"==typeof e.width?e.width:i.width,height:"number"==typeof e.height?e.height:i.height,fillColor:e.fillColor||i.fill,strokeColor:e.strokeColor||i.stroke};var r}),r=(e.edges||[]).map((e,t)=>({id:e.id||"edge-"+t,sourceId:e.source,targetId:e.target,sourcePort:e.sourcePort||"B",targetPort:e.targetPort||"T",label:e.label||"",linkShape:e.linkShape||"visio"})),s=t.layout||"layered";return"none"!==s&&i.some(e=>Number.isNaN(e.left)||Number.isNaN(e.top))&&(i.forEach(e=>{Number.isNaN(e.left)&&(e.left=0),Number.isNaN(e.top)&&(e.top=0)}),function autoLayout(e,t,i,o){const r=new Map;e.forEach(e=>r.set(e.id,e));const s=new Map,n=new Map;e.forEach(e=>{s.set(e.id,0),n.set(e.id,[])}),t.forEach(e=>{r.has(e.sourceId)&&r.has(e.targetId)&&(s.set(e.targetId,(s.get(e.targetId)||0)+1),n.get(e.sourceId).push(e.targetId))});const a=new Map,l=[];e.forEach(e=>{0===(s.get(e.id)||0)&&(a.set(e.id,0),l.push(e.id))}),!l.length&&e.length&&(a.set(e[0].id,0),l.push(e[0].id));let d=e.length*e.length+e.length;for(;l.length&&d-- >0;){const e=l.shift(),t=a.get(e)||0;(n.get(e)||[]).forEach(e=>{const i=t+1;(a.get(e)??-1)<i&&(a.set(e,i),l.push(e))})}e.forEach(e=>{a.has(e.id)||a.set(e.id,0)});const p=new Map;e.forEach(e=>{const t=a.get(e.id)||0;p.has(t)||p.set(t,[]),p.get(t).push(e)});const h=[...p.keys()].sort((e,t)=>e-t),c=h.map(e=>Math.max(...p.get(e).map(e=>e.height)));c.reduce((e,t)=>e+t,0),Math.max(h.length-1,0);let u=0;h.forEach((e,t)=>{const r=p.get(e);let s=-(r.reduce((e,t)=>e+t.width,0)+Math.max(r.length-1,0)*i)/2;r.forEach(e=>{e.left=Math.round(s),e.top=Math.round(u+(c[t]-e.height)/2),s+=e.width+i}),u+=c[t]+o});const f=Math.min(...e.map(e=>e.left)),m=Math.min(...e.map(e=>e.top));e.forEach(e=>{e.left+=80-f,e.top+=80-m})}(i,r,Number(t.gapX)||90,Number(t.gapY)||90)),i.forEach(e=>{Number.isNaN(e.left)&&(e.left=0),Number.isNaN(e.top)&&(e.top=0)}),{kind:"flowchart",nodes:i,edges:r,layout:s,options:t}}function renderFlowDsl(t,i){const o=compileFlowDsl(i),r=o.options||{},s=(new e.ICE).init(t),n=new e.FlowDesigner(s);return o.nodes.forEach(e=>{n.createNode(e.kind,e)}),o.edges.forEach(e=>{n.createEdge(e)}),n.select(null),r.viewport?s.setViewport(r.viewport.scale,r.viewport.tx,r.viewport.ty):!1!==r.fitViewport&&n.fitViewport(r.fitViewportPadding),n.resetHistory(),{kind:"flowchart",ice:s,designer:n}}exports.DSL_SCHEMA_VERSION=1,exports.compileDsl=compileDsl,exports.compileFlowDsl=compileFlowDsl,exports.isFlowDsl=isFlowDsl,exports.renderDsl=function renderDsl(t,i){const o=validateDsl(i);if(!o.valid)throw new Error(o.errors.join("\n"));return isFlowDsl(i)?renderFlowDsl(t,i):function renderErDsl(t,i){const o=compileDsl(i),r=o.options||{},s=(new e.ICE).init(t),n=new e.EntityDesigner(s);o.entities.forEach(e=>{n.createEntity(e)}),function positionLinks(e,t,i){const o=new Map;return e.entities.forEach(e=>{o.set(e.state.id,e.getMinBoundingBox(!0))}),t.relations.map(e=>{const t=o.get(e.sourceId),r=o.get(e.targetId);let s="R",n="L";if(t&&r){const e=r.center[0]-t.center[0],i=r.center[1]-t.center[1];Math.abs(e)>Math.abs(i)?(s=e>=0?"R":"L",n=e>=0?"L":"R"):(s=i>=0?"B":"T",n=i>=0?"T":"B")}return{...e,routeType:e.routeType||i||"orthogonal",linkShape:e.linkShape||"visio",links:{start:{id:e.sourceId,position:s},end:{id:e.targetId,position:n}}}})}(n,o,r.routeType).forEach(e=>{n.createRelation(e)}),("layered"===o.layout||"horizontal"===o.layout)&&new e.ICELayeredLayout({gapX:r.gapX||120,gapY:r.gapY||50}).layoutContainer(s);r.viewport?s.setViewport(r.viewport.scale,r.viewport.tx,r.viewport.ty):r.fitViewport&&function fitViewport(e,t,i){const o=t.entities||[];let r=1/0,s=1/0,n=-1/0,a=-1/0;o.forEach(e=>{const t=e.getMinBoundingBox(!0).getMinAndMaxPoint();r=Math.min(r,t.minX),s=Math.min(s,t.minY),n=Math.max(n,t.maxX),a=Math.max(a,t.maxY)});const l=n-r,d=a-s,p=e.canvasWidth||0,h=e.canvasHeight||0;if(!p||!h||l<=0||d<=0)return;const c=Number.isFinite(i)&&i>=0?i:40,u=Math.max(1,p-2*c),f=Math.max(1,h-2*c),m=Math.min(u/l,f/d,1),g=(p-l*m)/2-r*m,y=(h-d*m)/2-s*m;e.setViewport(m,g,y)}(s,n,r.fitViewportPadding);return{kind:"entity",ice:s,designer:n}}(t,i)},exports.renderFlowDsl=renderFlowDsl,exports.validateDsl=validateDsl,exports.validateFlowDsl=validateFlowDsl;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{ICE as e,EntityDesigner as t,ICELayeredLayout as i}from"ice-entity-designer";const r=1;function validateDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};if(void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion),Array.isArray(e.entities)){const i=new Set;e.entities.forEach((e,r)=>{const n=`entities[${r}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?i.has(e.id)?t.push(`${n}.id is duplicated: ${e.id}`):i.add(e.id):t.push(n+".id must be a non-empty string"),Array.isArray(e.fields)?e.fields.forEach((e,i)=>{e&&"object"==typeof e&&"string"==typeof e.name&&e.name.trim()||t.push(`${n}.fields[${i}].name must be a non-empty string`)}):t.push(n+".fields must be an array")):t.push(n+" must be an object")})}else t.push("entities must be an array");return void 0===e.relations||Array.isArray(e.relations)?(e.relations||[]).forEach((e,i)=>{const r=`relations[${i}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()||t.push(r+".source must be a non-empty string"),"string"==typeof e.target&&e.target.trim()||t.push(r+".target must be a non-empty string")):t.push(r+" must be an object")}):t.push("relations must be an array"),{valid:0===t.length,errors:t}}function entityName(e){return e.entityName||e.name||e.id}function relationType(e){return e.relationType||e.type||"one-to-many"}function compileDsl(e){return{entities:e.entities.map(e=>({id:e.id,entityName:entityName(e),fields:e.fields||[],left:e.left,top:e.top,width:e.width,height:e.height,style:e.style,headerStyle:e.headerStyle,fieldStyle:e.fieldStyle,dividerStyle:e.dividerStyle,draggable:e.draggable,interactive:e.interactive})),relations:(e.relations||[]).map((e,t)=>({id:e.id||"relation-"+t,relationType:relationType(e),sourceId:e.source,targetId:e.target,sourceField:e.sourceField||"id",targetField:e.targetField||"id",nullable:e.nullable,onDelete:e.onDelete,onUpdate:e.onUpdate,joinTableName:e.joinTableName,fromKey:e.fromKey,toKey:e.toKey,sourceCardinality:e.sourceCardinality,targetCardinality:e.targetCardinality,label:e.label,style:e.style,arrow:e.arrow,linkShape:e.linkShape,routeType:e.routeType,routeOffset:e.routeOffset,curveType:e.curveType,lineDash:e.lineDash})),layout:e.layout,options:e.options}}function renderDsl(r,n){const a=validateDsl(n);if(!a.valid)throw new Error(a.errors.join("\n"));const o=compileDsl(n),s=(new e).init(r),l=new t(s);if(o.entities.forEach(e=>{l.createEntity(e)}),function positionLinks(e,t){const i=new Map;return e.entities.forEach(e=>{i.set(e.state.id,e.getMinBoundingBox(!0))}),t.relations.map(e=>{const t=i.get(e.sourceId),r=i.get(e.targetId);let n="R",a="L";if(t&&r){const e=r.center[0]-t.center[0],i=r.center[1]-t.center[1];Math.abs(e)>Math.abs(i)?(n=e>=0?"R":"L",a=e>=0?"L":"R"):(n=i>=0?"B":"T",a=i>=0?"T":"B")}return{...e,routeType:e.routeType||"orthogonal",linkShape:e.linkShape||"visio",links:{start:{id:e.sourceId,position:n},end:{id:e.targetId,position:a}}}})}(l,o).forEach(e=>{l.createRelation(e)}),"layered"===o.layout||"horizontal"===o.layout){const e=o.options||{};new i({gapX:e.gapX||120,gapY:e.gapY||50}).layoutContainer(s)}return{ice:s,designer:l}}export{r as DSL_SCHEMA_VERSION,compileDsl,renderDsl,validateDsl};
1
+ import{FLOW_NODE_KINDS as e,ICE as t,FlowDesigner as i,EntityDesigner as o,ICELayeredLayout as r}from"ice-entity-designer";function isFlowDsl(e){return!!e&&"object"==typeof e&&"flowchart"===e.kind}const n=1,s=["terminator","process","decision","io"],a=["T","R","B","L","C"];function validateDsl(e){return isFlowDsl(e)?validateFlowDsl(e):function validateErDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion);if(Array.isArray(e.entities)){const i=new Set;e.entities.forEach((e,o)=>{const r=`entities[${o}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?i.has(e.id)?t.push(`${r}.id is duplicated: ${e.id}`):i.add(e.id):t.push(r+".id must be a non-empty string"),Array.isArray(e.fields)?e.fields.forEach((e,i)=>{e&&"object"==typeof e&&"string"==typeof e.name&&e.name.trim()||t.push(`${r}.fields[${i}].name must be a non-empty string`)}):t.push(r+".fields must be an array")):t.push(r+" must be an object")})}else t.push("entities must be an array");void 0===e.relations||Array.isArray(e.relations)?(e.relations||[]).forEach((e,i)=>{const o=`relations[${i}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()||t.push(o+".source must be a non-empty string"),"string"==typeof e.target&&e.target.trim()||t.push(o+".target must be a non-empty string")):t.push(o+" must be an object")}):t.push("relations must be an array");return{valid:0===t.length,errors:t}}(e)}function validateFlowDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion);const i=new Set;return Array.isArray(e.nodes)?e.nodes.forEach((e,o)=>{const r=`nodes[${o}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?i.has(e.id)?t.push(`${r}.id is duplicated: ${e.id}`):i.add(e.id):t.push(r+".id must be a non-empty string"),void 0!==e.kind&&-1===s.indexOf(e.kind)&&t.push(`${r}.kind must be one of ${s.join("/")}`),["left","top","width","height"].forEach(i=>{const o=e[i];void 0!==o&&"number"!=typeof o&&t.push(`${r}.${i} must be a number when present`)})):t.push(r+" must be an object")}):t.push("nodes must be an array"),void 0===e.edges||Array.isArray(e.edges)?(e.edges||[]).forEach((e,o)=>{const r=`edges[${o}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()&&i.has(e.source)||t.push(r+".source must reference an existing node id"),"string"==typeof e.target&&e.target.trim()&&i.has(e.target)||t.push(r+".target must reference an existing node id"),void 0!==e.label&&"string"!=typeof e.label&&t.push(r+".label must be a string when present"),void 0!==e.sourcePort&&-1===a.indexOf(e.sourcePort)&&t.push(`${r}.sourcePort must be one of ${a.join("/")}`),void 0!==e.targetPort&&-1===a.indexOf(e.targetPort)&&t.push(`${r}.targetPort must be one of ${a.join("/")}`),void 0!==e.linkShape&&"visio"!==e.linkShape&&"bezier"!==e.linkShape&&t.push(r+'.linkShape must be "visio" or "bezier" when present')):t.push(r+" must be an object")}):t.push("edges must be an array when present"),{valid:0===t.length,errors:t}}function entityName(e){return e.entityName||e.name||e.id}function relationType(e){return e.relationType||e.type||"one-to-many"}function compileDsl(e){if(isFlowDsl(e))throw new Error("compileDsl() 只处理 ER 文档;流程图文档请使用 compileFlowDsl()");if(!e||!Array.isArray(e.entities))throw new Error("ER 文档必须包含 entities 数组");return{entities:e.entities.map(e=>({id:e.id,entityName:entityName(e),fields:e.fields||[],left:e.left,top:e.top,width:e.width,height:e.height,style:e.style,headerStyle:e.headerStyle,fieldStyle:e.fieldStyle,dividerStyle:e.dividerStyle,draggable:e.draggable,interactive:e.interactive})),relations:(e.relations||[]).map((e,t)=>({id:e.id||"relation-"+t,relationType:relationType(e),sourceId:e.source,targetId:e.target,sourceField:e.sourceField||"id",targetField:e.targetField||"id",nullable:e.nullable,onDelete:e.onDelete,onUpdate:e.onUpdate,joinTableName:e.joinTableName,fromKey:e.fromKey,toKey:e.toKey,sourceCardinality:e.sourceCardinality,targetCardinality:e.targetCardinality,label:e.label,style:e.style,arrow:e.arrow,linkShape:e.linkShape,routeType:e.routeType,routeOffset:e.routeOffset,curveType:e.curveType,lineDash:e.lineDash})),layout:e.layout,options:e.options}}const l={terminator:{width:e.terminator.width,height:e.terminator.height,fill:e.terminator.fill,stroke:e.terminator.stroke},process:{width:e.process.width,height:e.process.height,fill:e.process.fill,stroke:e.process.stroke},decision:{width:e.decision.width,height:e.decision.height,fill:e.decision.fill,stroke:e.decision.stroke},io:{width:e.io.width,height:e.io.height,fill:e.io.fill,stroke:e.io.stroke}};function compileFlowDsl(e){const t=e.options||{},i=(e.nodes||[]).map(e=>{const t=function nodeKind(e){return e.kind&&l[e.kind]?e.kind:"process"}(e),i=l[t];return{id:e.id,typeId:"FlowNode",kind:t,title:(o=e,o.title||o.name||o.id),left:"number"==typeof e.left?e.left:Number.NaN,top:"number"==typeof e.top?e.top:Number.NaN,width:"number"==typeof e.width?e.width:i.width,height:"number"==typeof e.height?e.height:i.height,fillColor:e.fillColor||i.fill,strokeColor:e.strokeColor||i.stroke};var o}),o=(e.edges||[]).map((e,t)=>({id:e.id||"edge-"+t,sourceId:e.source,targetId:e.target,sourcePort:e.sourcePort||"B",targetPort:e.targetPort||"T",label:e.label||"",linkShape:e.linkShape||"visio"})),r=t.layout||"layered";return"none"!==r&&i.some(e=>Number.isNaN(e.left)||Number.isNaN(e.top))&&(i.forEach(e=>{Number.isNaN(e.left)&&(e.left=0),Number.isNaN(e.top)&&(e.top=0)}),function autoLayout(e,t,i,o){const r=new Map;e.forEach(e=>r.set(e.id,e));const n=new Map,s=new Map;e.forEach(e=>{n.set(e.id,0),s.set(e.id,[])}),t.forEach(e=>{r.has(e.sourceId)&&r.has(e.targetId)&&(n.set(e.targetId,(n.get(e.targetId)||0)+1),s.get(e.sourceId).push(e.targetId))});const a=new Map,l=[];e.forEach(e=>{0===(n.get(e.id)||0)&&(a.set(e.id,0),l.push(e.id))}),!l.length&&e.length&&(a.set(e[0].id,0),l.push(e[0].id));let d=e.length*e.length+e.length;for(;l.length&&d-- >0;){const e=l.shift(),t=a.get(e)||0;(s.get(e)||[]).forEach(e=>{const i=t+1;(a.get(e)??-1)<i&&(a.set(e,i),l.push(e))})}e.forEach(e=>{a.has(e.id)||a.set(e.id,0)});const h=new Map;e.forEach(e=>{const t=a.get(e.id)||0;h.has(t)||h.set(t,[]),h.get(t).push(e)});const p=[...h.keys()].sort((e,t)=>e-t),c=p.map(e=>Math.max(...h.get(e).map(e=>e.height)));c.reduce((e,t)=>e+t,0),Math.max(p.length-1,0);let u=0;p.forEach((e,t)=>{const r=h.get(e);let n=-(r.reduce((e,t)=>e+t.width,0)+Math.max(r.length-1,0)*i)/2;r.forEach(e=>{e.left=Math.round(n),e.top=Math.round(u+(c[t]-e.height)/2),n+=e.width+i}),u+=c[t]+o});const f=Math.min(...e.map(e=>e.left)),m=Math.min(...e.map(e=>e.top));e.forEach(e=>{e.left+=80-f,e.top+=80-m})}(i,o,Number(t.gapX)||90,Number(t.gapY)||90)),i.forEach(e=>{Number.isNaN(e.left)&&(e.left=0),Number.isNaN(e.top)&&(e.top=0)}),{kind:"flowchart",nodes:i,edges:o,layout:r,options:t}}function renderDsl(e,i){const n=validateDsl(i);if(!n.valid)throw new Error(n.errors.join("\n"));return isFlowDsl(i)?renderFlowDsl(e,i):function renderErDsl(e,i){const n=compileDsl(i),s=n.options||{},a=(new t).init(e),l=new o(a);n.entities.forEach(e=>{l.createEntity(e)}),function positionLinks(e,t,i){const o=new Map;return e.entities.forEach(e=>{o.set(e.state.id,e.getMinBoundingBox(!0))}),t.relations.map(e=>{const t=o.get(e.sourceId),r=o.get(e.targetId);let n="R",s="L";if(t&&r){const e=r.center[0]-t.center[0],i=r.center[1]-t.center[1];Math.abs(e)>Math.abs(i)?(n=e>=0?"R":"L",s=e>=0?"L":"R"):(n=i>=0?"B":"T",s=i>=0?"T":"B")}return{...e,routeType:e.routeType||i||"orthogonal",linkShape:e.linkShape||"visio",links:{start:{id:e.sourceId,position:n},end:{id:e.targetId,position:s}}}})}(l,n,s.routeType).forEach(e=>{l.createRelation(e)}),("layered"===n.layout||"horizontal"===n.layout)&&new r({gapX:s.gapX||120,gapY:s.gapY||50}).layoutContainer(a);s.viewport?a.setViewport(s.viewport.scale,s.viewport.tx,s.viewport.ty):s.fitViewport&&function fitViewport(e,t,i){const o=t.entities||[];let r=1/0,n=1/0,s=-1/0,a=-1/0;o.forEach(e=>{const t=e.getMinBoundingBox(!0).getMinAndMaxPoint();r=Math.min(r,t.minX),n=Math.min(n,t.minY),s=Math.max(s,t.maxX),a=Math.max(a,t.maxY)});const l=s-r,d=a-n,h=e.canvasWidth||0,p=e.canvasHeight||0;if(!h||!p||l<=0||d<=0)return;const c=Number.isFinite(i)&&i>=0?i:40,u=Math.max(1,h-2*c),f=Math.max(1,p-2*c),m=Math.min(u/l,f/d,1),g=(h-l*m)/2-r*m,y=(p-d*m)/2-n*m;e.setViewport(m,g,y)}(a,l,s.fitViewportPadding);return{kind:"entity",ice:a,designer:l}}(e,i)}function renderFlowDsl(e,o){const r=compileFlowDsl(o),n=r.options||{},s=(new t).init(e),a=new i(s);return r.nodes.forEach(e=>{a.createNode(e.kind,e)}),r.edges.forEach(e=>{a.createEdge(e)}),a.select(null),n.viewport?s.setViewport(n.viewport.scale,n.viewport.tx,n.viewport.ty):!1!==n.fitViewport&&a.fitViewport(n.fitViewportPadding),a.resetHistory(),{kind:"flowchart",ice:s,designer:a}}export{n as DSL_SCHEMA_VERSION,compileDsl,compileFlowDsl,isFlowDsl,renderDsl,renderFlowDsl,validateDsl,validateFlowDsl};
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("ice-entity-designer")):"function"==typeof define&&define.amd?define(["exports","ice-entity-designer"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ICEDSL={},e.IED)}(this,(function(e,t){"use strict";function validateDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};if(void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion),Array.isArray(e.entities)){const i=new Set;e.entities.forEach((e,r)=>{const n=`entities[${r}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?i.has(e.id)?t.push(`${n}.id is duplicated: ${e.id}`):i.add(e.id):t.push(n+".id must be a non-empty string"),Array.isArray(e.fields)?e.fields.forEach((e,i)=>{e&&"object"==typeof e&&"string"==typeof e.name&&e.name.trim()||t.push(`${n}.fields[${i}].name must be a non-empty string`)}):t.push(n+".fields must be an array")):t.push(n+" must be an object")})}else t.push("entities must be an array");return void 0===e.relations||Array.isArray(e.relations)?(e.relations||[]).forEach((e,i)=>{const r=`relations[${i}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()||t.push(r+".source must be a non-empty string"),"string"==typeof e.target&&e.target.trim()||t.push(r+".target must be a non-empty string")):t.push(r+" must be an object")}):t.push("relations must be an array"),{valid:0===t.length,errors:t}}function entityName(e){return e.entityName||e.name||e.id}function relationType(e){return e.relationType||e.type||"one-to-many"}function compileDsl(e){return{entities:e.entities.map(e=>({id:e.id,entityName:entityName(e),fields:e.fields||[],left:e.left,top:e.top,width:e.width,height:e.height,style:e.style,headerStyle:e.headerStyle,fieldStyle:e.fieldStyle,dividerStyle:e.dividerStyle,draggable:e.draggable,interactive:e.interactive})),relations:(e.relations||[]).map((e,t)=>({id:e.id||"relation-"+t,relationType:relationType(e),sourceId:e.source,targetId:e.target,sourceField:e.sourceField||"id",targetField:e.targetField||"id",nullable:e.nullable,onDelete:e.onDelete,onUpdate:e.onUpdate,joinTableName:e.joinTableName,fromKey:e.fromKey,toKey:e.toKey,sourceCardinality:e.sourceCardinality,targetCardinality:e.targetCardinality,label:e.label,style:e.style,arrow:e.arrow,linkShape:e.linkShape,routeType:e.routeType,routeOffset:e.routeOffset,curveType:e.curveType,lineDash:e.lineDash})),layout:e.layout,options:e.options}}e.DSL_SCHEMA_VERSION=1,e.compileDsl=compileDsl,e.renderDsl=function renderDsl(e,i){const r=validateDsl(i);if(!r.valid)throw new Error(r.errors.join("\n"));const n=compileDsl(i),o=(new t.ICE).init(e),a=new t.EntityDesigner(o);if(n.entities.forEach(e=>{a.createEntity(e)}),function positionLinks(e,t){const i=new Map;return e.entities.forEach(e=>{i.set(e.state.id,e.getMinBoundingBox(!0))}),t.relations.map(e=>{const t=i.get(e.sourceId),r=i.get(e.targetId);let n="R",o="L";if(t&&r){const e=r.center[0]-t.center[0],i=r.center[1]-t.center[1];Math.abs(e)>Math.abs(i)?(n=e>=0?"R":"L",o=e>=0?"L":"R"):(n=i>=0?"B":"T",o=i>=0?"T":"B")}return{...e,routeType:e.routeType||"orthogonal",linkShape:e.linkShape||"visio",links:{start:{id:e.sourceId,position:n},end:{id:e.targetId,position:o}}}})}(a,n).forEach(e=>{a.createRelation(e)}),"layered"===n.layout||"horizontal"===n.layout){const e=n.options||{};new t.ICELayeredLayout({gapX:e.gapX||120,gapY:e.gapY||50}).layoutContainer(o)}return{ice:o,designer:a}},e.validateDsl=validateDsl,Object.defineProperty(e,"__esModule",{value:!0})}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("ice-entity-designer")):"function"==typeof define&&define.amd?define(["exports","ice-entity-designer"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ICEDSL={},e.IED)}(this,(function(e,t){"use strict";function isFlowDsl(e){return!!e&&"object"==typeof e&&"flowchart"===e.kind}const i=["terminator","process","decision","io"],o=["T","R","B","L","C"];function validateDsl(e){return isFlowDsl(e)?validateFlowDsl(e):function validateErDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion);if(Array.isArray(e.entities)){const i=new Set;e.entities.forEach((e,o)=>{const r=`entities[${o}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?i.has(e.id)?t.push(`${r}.id is duplicated: ${e.id}`):i.add(e.id):t.push(r+".id must be a non-empty string"),Array.isArray(e.fields)?e.fields.forEach((e,i)=>{e&&"object"==typeof e&&"string"==typeof e.name&&e.name.trim()||t.push(`${r}.fields[${i}].name must be a non-empty string`)}):t.push(r+".fields must be an array")):t.push(r+" must be an object")})}else t.push("entities must be an array");void 0===e.relations||Array.isArray(e.relations)?(e.relations||[]).forEach((e,i)=>{const o=`relations[${i}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()||t.push(o+".source must be a non-empty string"),"string"==typeof e.target&&e.target.trim()||t.push(o+".target must be a non-empty string")):t.push(o+" must be an object")}):t.push("relations must be an array");return{valid:0===t.length,errors:t}}(e)}function validateFlowDsl(e){const t=[];if(!e||"object"!=typeof e||Array.isArray(e))return{valid:!1,errors:["DSL root must be an object"]};void 0!==e.schemaVersion&&1!==e.schemaVersion&&t.push("Unsupported schemaVersion: "+e.schemaVersion);const r=new Set;return Array.isArray(e.nodes)?e.nodes.forEach((e,o)=>{const n=`nodes[${o}]`;e&&"object"==typeof e?("string"==typeof e.id&&e.id.trim()?r.has(e.id)?t.push(`${n}.id is duplicated: ${e.id}`):r.add(e.id):t.push(n+".id must be a non-empty string"),void 0!==e.kind&&-1===i.indexOf(e.kind)&&t.push(`${n}.kind must be one of ${i.join("/")}`),["left","top","width","height"].forEach(i=>{const o=e[i];void 0!==o&&"number"!=typeof o&&t.push(`${n}.${i} must be a number when present`)})):t.push(n+" must be an object")}):t.push("nodes must be an array"),void 0===e.edges||Array.isArray(e.edges)?(e.edges||[]).forEach((e,i)=>{const n=`edges[${i}]`;e&&"object"==typeof e?("string"==typeof e.source&&e.source.trim()&&r.has(e.source)||t.push(n+".source must reference an existing node id"),"string"==typeof e.target&&e.target.trim()&&r.has(e.target)||t.push(n+".target must reference an existing node id"),void 0!==e.label&&"string"!=typeof e.label&&t.push(n+".label must be a string when present"),void 0!==e.sourcePort&&-1===o.indexOf(e.sourcePort)&&t.push(`${n}.sourcePort must be one of ${o.join("/")}`),void 0!==e.targetPort&&-1===o.indexOf(e.targetPort)&&t.push(`${n}.targetPort must be one of ${o.join("/")}`),void 0!==e.linkShape&&"visio"!==e.linkShape&&"bezier"!==e.linkShape&&t.push(n+'.linkShape must be "visio" or "bezier" when present')):t.push(n+" must be an object")}):t.push("edges must be an array when present"),{valid:0===t.length,errors:t}}function entityName(e){return e.entityName||e.name||e.id}function relationType(e){return e.relationType||e.type||"one-to-many"}function compileDsl(e){if(isFlowDsl(e))throw new Error("compileDsl() 只处理 ER 文档;流程图文档请使用 compileFlowDsl()");if(!e||!Array.isArray(e.entities))throw new Error("ER 文档必须包含 entities 数组");return{entities:e.entities.map(e=>({id:e.id,entityName:entityName(e),fields:e.fields||[],left:e.left,top:e.top,width:e.width,height:e.height,style:e.style,headerStyle:e.headerStyle,fieldStyle:e.fieldStyle,dividerStyle:e.dividerStyle,draggable:e.draggable,interactive:e.interactive})),relations:(e.relations||[]).map((e,t)=>({id:e.id||"relation-"+t,relationType:relationType(e),sourceId:e.source,targetId:e.target,sourceField:e.sourceField||"id",targetField:e.targetField||"id",nullable:e.nullable,onDelete:e.onDelete,onUpdate:e.onUpdate,joinTableName:e.joinTableName,fromKey:e.fromKey,toKey:e.toKey,sourceCardinality:e.sourceCardinality,targetCardinality:e.targetCardinality,label:e.label,style:e.style,arrow:e.arrow,linkShape:e.linkShape,routeType:e.routeType,routeOffset:e.routeOffset,curveType:e.curveType,lineDash:e.lineDash})),layout:e.layout,options:e.options}}const r={terminator:{width:t.FLOW_NODE_KINDS.terminator.width,height:t.FLOW_NODE_KINDS.terminator.height,fill:t.FLOW_NODE_KINDS.terminator.fill,stroke:t.FLOW_NODE_KINDS.terminator.stroke},process:{width:t.FLOW_NODE_KINDS.process.width,height:t.FLOW_NODE_KINDS.process.height,fill:t.FLOW_NODE_KINDS.process.fill,stroke:t.FLOW_NODE_KINDS.process.stroke},decision:{width:t.FLOW_NODE_KINDS.decision.width,height:t.FLOW_NODE_KINDS.decision.height,fill:t.FLOW_NODE_KINDS.decision.fill,stroke:t.FLOW_NODE_KINDS.decision.stroke},io:{width:t.FLOW_NODE_KINDS.io.width,height:t.FLOW_NODE_KINDS.io.height,fill:t.FLOW_NODE_KINDS.io.fill,stroke:t.FLOW_NODE_KINDS.io.stroke}};function compileFlowDsl(e){const t=e.options||{},i=(e.nodes||[]).map(e=>{const t=function nodeKind(e){return e.kind&&r[e.kind]?e.kind:"process"}(e),i=r[t];return{id:e.id,typeId:"FlowNode",kind:t,title:(o=e,o.title||o.name||o.id),left:"number"==typeof e.left?e.left:Number.NaN,top:"number"==typeof e.top?e.top:Number.NaN,width:"number"==typeof e.width?e.width:i.width,height:"number"==typeof e.height?e.height:i.height,fillColor:e.fillColor||i.fill,strokeColor:e.strokeColor||i.stroke};var o}),o=(e.edges||[]).map((e,t)=>({id:e.id||"edge-"+t,sourceId:e.source,targetId:e.target,sourcePort:e.sourcePort||"B",targetPort:e.targetPort||"T",label:e.label||"",linkShape:e.linkShape||"visio"})),n=t.layout||"layered";return"none"!==n&&i.some(e=>Number.isNaN(e.left)||Number.isNaN(e.top))&&(i.forEach(e=>{Number.isNaN(e.left)&&(e.left=0),Number.isNaN(e.top)&&(e.top=0)}),function autoLayout(e,t,i,o){const r=new Map;e.forEach(e=>r.set(e.id,e));const n=new Map,s=new Map;e.forEach(e=>{n.set(e.id,0),s.set(e.id,[])}),t.forEach(e=>{r.has(e.sourceId)&&r.has(e.targetId)&&(n.set(e.targetId,(n.get(e.targetId)||0)+1),s.get(e.sourceId).push(e.targetId))});const a=new Map,l=[];e.forEach(e=>{0===(n.get(e.id)||0)&&(a.set(e.id,0),l.push(e.id))}),!l.length&&e.length&&(a.set(e[0].id,0),l.push(e[0].id));let d=e.length*e.length+e.length;for(;l.length&&d-- >0;){const e=l.shift(),t=a.get(e)||0;(s.get(e)||[]).forEach(e=>{const i=t+1;(a.get(e)??-1)<i&&(a.set(e,i),l.push(e))})}e.forEach(e=>{a.has(e.id)||a.set(e.id,0)});const h=new Map;e.forEach(e=>{const t=a.get(e.id)||0;h.has(t)||h.set(t,[]),h.get(t).push(e)});const p=[...h.keys()].sort((e,t)=>e-t),c=p.map(e=>Math.max(...h.get(e).map(e=>e.height)));c.reduce((e,t)=>e+t,0),Math.max(p.length-1,0);let u=0;p.forEach((e,t)=>{const r=h.get(e);let n=-(r.reduce((e,t)=>e+t.width,0)+Math.max(r.length-1,0)*i)/2;r.forEach(e=>{e.left=Math.round(n),e.top=Math.round(u+(c[t]-e.height)/2),n+=e.width+i}),u+=c[t]+o});const f=Math.min(...e.map(e=>e.left)),m=Math.min(...e.map(e=>e.top));e.forEach(e=>{e.left+=80-f,e.top+=80-m})}(i,o,Number(t.gapX)||90,Number(t.gapY)||90)),i.forEach(e=>{Number.isNaN(e.left)&&(e.left=0),Number.isNaN(e.top)&&(e.top=0)}),{kind:"flowchart",nodes:i,edges:o,layout:n,options:t}}function renderFlowDsl(e,i){const o=compileFlowDsl(i),r=o.options||{},n=(new t.ICE).init(e),s=new t.FlowDesigner(n);return o.nodes.forEach(e=>{s.createNode(e.kind,e)}),o.edges.forEach(e=>{s.createEdge(e)}),s.select(null),r.viewport?n.setViewport(r.viewport.scale,r.viewport.tx,r.viewport.ty):!1!==r.fitViewport&&s.fitViewport(r.fitViewportPadding),s.resetHistory(),{kind:"flowchart",ice:n,designer:s}}e.DSL_SCHEMA_VERSION=1,e.compileDsl=compileDsl,e.compileFlowDsl=compileFlowDsl,e.isFlowDsl=isFlowDsl,e.renderDsl=function renderDsl(e,i){const o=validateDsl(i);if(!o.valid)throw new Error(o.errors.join("\n"));return isFlowDsl(i)?renderFlowDsl(e,i):function renderErDsl(e,i){const o=compileDsl(i),r=o.options||{},n=(new t.ICE).init(e),s=new t.EntityDesigner(n);o.entities.forEach(e=>{s.createEntity(e)}),function positionLinks(e,t,i){const o=new Map;return e.entities.forEach(e=>{o.set(e.state.id,e.getMinBoundingBox(!0))}),t.relations.map(e=>{const t=o.get(e.sourceId),r=o.get(e.targetId);let n="R",s="L";if(t&&r){const e=r.center[0]-t.center[0],i=r.center[1]-t.center[1];Math.abs(e)>Math.abs(i)?(n=e>=0?"R":"L",s=e>=0?"L":"R"):(n=i>=0?"B":"T",s=i>=0?"T":"B")}return{...e,routeType:e.routeType||i||"orthogonal",linkShape:e.linkShape||"visio",links:{start:{id:e.sourceId,position:n},end:{id:e.targetId,position:s}}}})}(s,o,r.routeType).forEach(e=>{s.createRelation(e)}),("layered"===o.layout||"horizontal"===o.layout)&&new t.ICELayeredLayout({gapX:r.gapX||120,gapY:r.gapY||50}).layoutContainer(n);r.viewport?n.setViewport(r.viewport.scale,r.viewport.tx,r.viewport.ty):r.fitViewport&&function fitViewport(e,t,i){const o=t.entities||[];let r=1/0,n=1/0,s=-1/0,a=-1/0;o.forEach(e=>{const t=e.getMinBoundingBox(!0).getMinAndMaxPoint();r=Math.min(r,t.minX),n=Math.min(n,t.minY),s=Math.max(s,t.maxX),a=Math.max(a,t.maxY)});const l=s-r,d=a-n,h=e.canvasWidth||0,p=e.canvasHeight||0;if(!h||!p||l<=0||d<=0)return;const c=Number.isFinite(i)&&i>=0?i:40,u=Math.max(1,h-2*c),f=Math.max(1,p-2*c),m=Math.min(u/l,f/d,1),g=(h-l*m)/2-r*m,y=(p-d*m)/2-n*m;e.setViewport(m,g,y)}(n,s,r.fitViewportPadding);return{kind:"entity",ice:n,designer:s}}(e,i)},e.renderFlowDsl=renderFlowDsl,e.validateDsl=validateDsl,e.validateFlowDsl=validateFlowDsl,Object.defineProperty(e,"__esModule",{value:!0})}));
@@ -1,8 +1,9 @@
1
- import type { DslDocument } from '../types';
1
+ import type { DslErDocument } from '../types';
2
2
  export declare type CompiledScene = {
3
3
  entities: Array<Record<string, any>>;
4
4
  relations: Array<Record<string, any>>;
5
5
  layout?: string;
6
6
  options?: Record<string, any>;
7
7
  };
8
- export declare function compileDsl(dsl: DslDocument): CompiledScene;
8
+ /** ER 文档 Entity / Relation 构造参数(流程图请用 compileFlowDsl) */
9
+ export declare function compileDsl(dsl: DslErDocument): CompiledScene;
@@ -0,0 +1,37 @@
1
+ import type { DslFlowDocument, DslFlowNodeKind } from '../types';
2
+ export declare type CompiledFlowNode = {
3
+ id: string;
4
+ typeId: string;
5
+ kind: DslFlowNodeKind;
6
+ title: string;
7
+ left: number;
8
+ top: number;
9
+ width: number;
10
+ height: number;
11
+ fillColor: string;
12
+ strokeColor: string;
13
+ };
14
+ export declare type CompiledFlowEdge = {
15
+ id: string;
16
+ sourceId: string;
17
+ targetId: string;
18
+ sourcePort: string;
19
+ targetPort: string;
20
+ label: string;
21
+ linkShape: string;
22
+ };
23
+ export declare type CompiledFlowScene = {
24
+ kind: 'flowchart';
25
+ nodes: CompiledFlowNode[];
26
+ edges: CompiledFlowEdge[];
27
+ layout: string;
28
+ options?: Record<string, any>;
29
+ };
30
+ /**
31
+ * 流程图 DSL → FlowNode / FlowEdge 构造参数。
32
+ *
33
+ * - 节点缺省尺寸/配色取 `FLOW_NODE_KINDS` 预设;
34
+ * - 只要有一个节点缺坐标(或显式 `layout: 'layered'`),就对全部节点做分层自动布局;
35
+ * - 连线的端口默认「下出上入」(B → T),分支标签走 label。
36
+ */
37
+ export declare function compileFlowDsl(dsl: DslFlowDocument): CompiledFlowScene;
@@ -1,6 +1,8 @@
1
1
  export * from './types';
2
- export { DSL_SCHEMA_VERSION, validateDsl } from './validate';
2
+ export { DSL_SCHEMA_VERSION, validateDsl, validateFlowDsl } from './validate';
3
3
  export { compileDsl } from './compiler/dslToScene';
4
4
  export type { CompiledScene } from './compiler/dslToScene';
5
- export { renderDsl } from './runtime/renderDsl';
6
- export type { RenderDslResult } from './runtime/renderDsl';
5
+ export { compileFlowDsl } from './compiler/flowToScene';
6
+ export type { CompiledFlowScene, CompiledFlowNode, CompiledFlowEdge } from './compiler/flowToScene';
7
+ export { renderDsl, renderFlowDsl } from './runtime/renderDsl';
8
+ export type { RenderDslResult, RenderErDslResult, RenderFlowDslResult } from './runtime/renderDsl';
@@ -1,6 +1,21 @@
1
- import type { DslDocument } from '../types';
2
- export declare type RenderDslResult = {
1
+ import type { DslDocument, DslFlowDocument } from '../types';
2
+ export declare type RenderErDslResult = {
3
+ kind: 'entity';
3
4
  ice: any;
4
5
  designer: any;
5
6
  };
7
+ export declare type RenderFlowDslResult = {
8
+ kind: 'flowchart';
9
+ ice: any;
10
+ designer: any;
11
+ };
12
+ export declare type RenderDslResult = RenderErDslResult | RenderFlowDslResult;
13
+ /**
14
+ * 渲染一份 DSL 文档:ER 文档(entities/relations)或流程图文档(kind: 'flowchart')。
15
+ *
16
+ * 两种文档共用同一个入口,返回的 `kind` 表明实际渲染的是哪一类,
17
+ * `designer` 分别是 `EntityDesigner` / `FlowDesigner` 实例。
18
+ */
6
19
  export declare function renderDsl(canvasOrId: any, dsl: DslDocument): RenderDslResult;
20
+ /** 渲染流程图文档:节点缺坐标时已由 compileFlowDsl 做过分层自动布局 */
21
+ export declare function renderFlowDsl(canvasOrId: any, dsl: DslFlowDocument): RenderFlowDslResult;
@@ -55,7 +55,8 @@ export declare type DslRelation = {
55
55
  lineDash?: number[];
56
56
  };
57
57
  export declare type DslLayout = 'grid' | 'layered' | 'horizontal' | 'vertical';
58
- export declare type DslDocument = {
58
+ /** ER 文档(实体 / 关系) */
59
+ export declare type DslErDocument = {
59
60
  schemaVersion?: number;
60
61
  entities: DslEntity[];
61
62
  relations?: DslRelation[];
@@ -67,7 +68,60 @@ export declare type DslDocument = {
67
68
  gapY?: number;
68
69
  };
69
70
  };
71
+ export declare type DslPort = 'T' | 'R' | 'B' | 'L' | 'C';
72
+ export declare type DslFlowNodeKind = 'terminator' | 'process' | 'decision' | 'io';
73
+ export declare type DslFlowNode = {
74
+ id: string;
75
+ /** 节点类型,默认 process */
76
+ kind?: DslFlowNodeKind;
77
+ /** 节点文字(title / name 二者取一,name 兼容 ER 文档写法) */
78
+ title?: string;
79
+ name?: string;
80
+ left?: number;
81
+ top?: number;
82
+ width?: number;
83
+ height?: number;
84
+ fillColor?: string;
85
+ strokeColor?: string;
86
+ };
87
+ export declare type DslFlowEdge = {
88
+ id?: string;
89
+ source: string;
90
+ target: string;
91
+ /** 分支标签,例如「是 / 否」 */
92
+ label?: string;
93
+ /** 连线离开源节点的位置,默认 B(下) */
94
+ sourcePort?: DslPort;
95
+ /** 连线进入目标节点的位置,默认 T(上) */
96
+ targetPort?: DslPort;
97
+ linkShape?: 'visio' | 'bezier';
98
+ };
99
+ export declare type DslFlowDocumentOptions = {
100
+ /** 是否自动适应视图,默认 true */
101
+ fitViewport?: boolean;
102
+ fitViewportPadding?: number;
103
+ viewport?: {
104
+ scale: number;
105
+ tx: number;
106
+ ty: number;
107
+ };
108
+ /** 节点缺坐标时的自动布局,默认 layered */
109
+ layout?: 'layered' | 'none';
110
+ gapX?: number;
111
+ gapY?: number;
112
+ };
113
+ export declare type DslFlowDocument = {
114
+ schemaVersion?: number;
115
+ kind: 'flowchart';
116
+ nodes: DslFlowNode[];
117
+ edges?: DslFlowEdge[];
118
+ options?: DslFlowDocumentOptions;
119
+ };
120
+ /** 一份 DSL 文档:ER(默认)或 流程图(kind: 'flowchart') */
121
+ export declare type DslDocument = DslErDocument | DslFlowDocument;
70
122
  export declare type DslValidationResult = {
71
123
  valid: boolean;
72
124
  errors: string[];
73
125
  };
126
+ /** 运行时判别:带 kind: 'flowchart' 的按流程图文档处理 */
127
+ export declare function isFlowDsl(dsl: any): dsl is DslFlowDocument;
@@ -1,3 +1,6 @@
1
- import type { DslDocument, DslValidationResult } from './types';
1
+ import type { DslDocument, DslFlowDocument, DslValidationResult } from './types';
2
2
  export declare const DSL_SCHEMA_VERSION = 1;
3
+ /** 校验一份 DSL 文档:ER 文档(entities/relations)或流程图文档(kind: 'flowchart' + nodes/edges) */
3
4
  export declare function validateDsl(dsl: DslDocument): DslValidationResult;
5
+ /** 流程图文档校验:结构 + 端点必须指向已存在的节点(与 ER 的关系校验同一口径) */
6
+ export declare function validateFlowDsl(dsl: DslFlowDocument): DslValidationResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ice-entity-designer-dsl",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A JSON-first DSL layer for AI agents to drive ice-entity-designer.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -19,7 +19,7 @@
19
19
  "types:check": "tsc --noEmit"
20
20
  },
21
21
  "dependencies": {
22
- "ice-entity-designer": "^0.0.17"
22
+ "ice-entity-designer": "^0.0.19"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@babel/core": "7.17.8",