callman-core 1.28.0 → 1.29.0
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/ai-packs/scenario-nodes.md +451 -0
- package/dist/ai/index.d.ts +2 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +1 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/outputSchemas.d.ts +3 -0
- package/dist/ai/outputSchemas.d.ts.map +1 -1
- package/dist/ai/outputSchemas.js +1 -0
- package/dist/ai/outputSchemas.js.map +1 -1
- package/dist/ai/parse/diagram.d.ts.map +1 -1
- package/dist/ai/parse/diagram.js +6 -3
- package/dist/ai/parse/diagram.js.map +1 -1
- package/dist/ai/prompts/diagram.d.ts +7 -3
- package/dist/ai/prompts/diagram.d.ts.map +1 -1
- package/dist/ai/prompts/diagram.js +44 -5
- package/dist/ai/prompts/diagram.js.map +1 -1
- package/dist/ai/prompts/scenario.d.ts.map +1 -1
- package/dist/ai/prompts/scenario.js +9 -8
- package/dist/ai/prompts/scenario.js.map +1 -1
- package/dist/ai/prompts/scenarioNodeReference.d.ts +16 -0
- package/dist/ai/prompts/scenarioNodeReference.d.ts.map +1 -0
- package/dist/ai/prompts/scenarioNodeReference.js +882 -0
- package/dist/ai/prompts/scenarioNodeReference.js.map +1 -0
- package/dist/ai/types.d.ts +6 -0
- package/dist/ai/types.d.ts.map +1 -1
- package/dist/diagram/bpmn/derive.d.ts +74 -0
- package/dist/diagram/bpmn/derive.d.ts.map +1 -0
- package/dist/diagram/bpmn/derive.js +392 -0
- package/dist/diagram/bpmn/derive.js.map +1 -0
- package/dist/diagram/bpmn/index.d.ts +7 -0
- package/dist/diagram/bpmn/index.d.ts.map +1 -0
- package/dist/diagram/bpmn/index.js +12 -0
- package/dist/diagram/bpmn/index.js.map +1 -0
- package/dist/diagram/bpmn/patch.d.ts +14 -0
- package/dist/diagram/bpmn/patch.d.ts.map +1 -0
- package/dist/diagram/bpmn/patch.js +770 -0
- package/dist/diagram/bpmn/patch.js.map +1 -0
- package/dist/diagram/bpmn/place.d.ts +4 -0
- package/dist/diagram/bpmn/place.d.ts.map +1 -0
- package/dist/diagram/bpmn/place.js +65 -0
- package/dist/diagram/bpmn/place.js.map +1 -0
- package/dist/diagram/bpmn/serialize.d.ts +12 -0
- package/dist/diagram/bpmn/serialize.d.ts.map +1 -0
- package/dist/diagram/bpmn/serialize.js +292 -0
- package/dist/diagram/bpmn/serialize.js.map +1 -0
- package/dist/diagram/bpmn/types.d.ts +42 -0
- package/dist/diagram/bpmn/types.d.ts.map +1 -0
- package/dist/diagram/bpmn/types.js +84 -0
- package/dist/diagram/bpmn/types.js.map +1 -0
- package/dist/diagram/bpmn/xml.d.ts +60 -0
- package/dist/diagram/bpmn/xml.d.ts.map +1 -0
- package/dist/diagram/bpmn/xml.js +342 -0
- package/dist/diagram/bpmn/xml.js.map +1 -0
- package/dist/diagram/elkGraph.d.ts +1 -3
- package/dist/diagram/elkGraph.d.ts.map +1 -1
- package/dist/diagram/elkGraph.js +1 -0
- package/dist/diagram/elkGraph.js.map +1 -1
- package/dist/diagram/fixtures.d.ts +10 -0
- package/dist/diagram/fixtures.d.ts.map +1 -0
- package/dist/diagram/fixtures.js +113 -0
- package/dist/diagram/fixtures.js.map +1 -0
- package/dist/diagram/geometry.d.ts +27 -0
- package/dist/diagram/geometry.d.ts.map +1 -0
- package/dist/diagram/geometry.js +122 -0
- package/dist/diagram/geometry.js.map +1 -0
- package/dist/diagram/history.d.ts +36 -0
- package/dist/diagram/history.d.ts.map +1 -0
- package/dist/diagram/history.js +54 -0
- package/dist/diagram/history.js.map +1 -0
- package/dist/diagram/ids.d.ts +5 -0
- package/dist/diagram/ids.d.ts.map +1 -0
- package/dist/diagram/ids.js +52 -0
- package/dist/diagram/ids.js.map +1 -0
- package/dist/diagram/index.d.ts +14 -2
- package/dist/diagram/index.d.ts.map +1 -1
- package/dist/diagram/index.js +20 -8
- package/dist/diagram/index.js.map +1 -1
- package/dist/diagram/layout.d.ts +107 -0
- package/dist/diagram/layout.d.ts.map +1 -0
- package/dist/diagram/layout.js +356 -0
- package/dist/diagram/layout.js.map +1 -0
- package/dist/diagram/mermaid/derive.d.ts +55 -0
- package/dist/diagram/mermaid/derive.d.ts.map +1 -0
- package/dist/diagram/mermaid/derive.js +343 -0
- package/dist/diagram/mermaid/derive.js.map +1 -0
- package/dist/diagram/mermaid/index.d.ts +8 -0
- package/dist/diagram/mermaid/index.d.ts.map +1 -0
- package/dist/diagram/mermaid/index.js +24 -0
- package/dist/diagram/mermaid/index.js.map +1 -0
- package/dist/diagram/mermaid/parse.d.ts +96 -0
- package/dist/diagram/mermaid/parse.d.ts.map +1 -0
- package/dist/diagram/mermaid/parse.js +672 -0
- package/dist/diagram/mermaid/parse.js.map +1 -0
- package/dist/diagram/mermaid/patch.d.ts +16 -0
- package/dist/diagram/mermaid/patch.d.ts.map +1 -0
- package/dist/diagram/mermaid/patch.js +463 -0
- package/dist/diagram/mermaid/patch.js.map +1 -0
- package/dist/diagram/mermaid/serialize.d.ts +14 -0
- package/dist/diagram/mermaid/serialize.d.ts.map +1 -0
- package/dist/diagram/mermaid/serialize.js +125 -0
- package/dist/diagram/mermaid/serialize.js.map +1 -0
- package/dist/diagram/mermaid/shapes.d.ts +11 -0
- package/dist/diagram/mermaid/shapes.d.ts.map +1 -0
- package/dist/diagram/mermaid/shapes.js +320 -0
- package/dist/diagram/mermaid/shapes.js.map +1 -0
- package/dist/diagram/migrations.d.ts +1 -0
- package/dist/diagram/migrations.d.ts.map +1 -1
- package/dist/diagram/migrations.js +7 -2
- package/dist/diagram/migrations.js.map +1 -1
- package/dist/diagram/normalize.d.ts +9 -1
- package/dist/diagram/normalize.d.ts.map +1 -1
- package/dist/diagram/normalize.js +194 -0
- package/dist/diagram/normalize.js.map +1 -1
- package/dist/diagram/ops.d.ts +146 -0
- package/dist/diagram/ops.d.ts.map +1 -0
- package/dist/diagram/ops.js +823 -0
- package/dist/diagram/ops.js.map +1 -0
- package/dist/diagram/opsReference.d.ts +3 -0
- package/dist/diagram/opsReference.d.ts.map +1 -0
- package/dist/diagram/opsReference.js +51 -0
- package/dist/diagram/opsReference.js.map +1 -0
- package/dist/diagram/patchInput.d.ts +193 -0
- package/dist/diagram/patchInput.d.ts.map +1 -0
- package/dist/diagram/patchInput.js +621 -0
- package/dist/diagram/patchInput.js.map +1 -0
- package/dist/diagram/routing/batch.d.ts +45 -0
- package/dist/diagram/routing/batch.d.ts.map +1 -0
- package/dist/diagram/routing/batch.js +136 -0
- package/dist/diagram/routing/batch.js.map +1 -0
- package/dist/diagram/routing/curves.d.ts +7 -0
- package/dist/diagram/routing/curves.d.ts.map +1 -0
- package/dist/diagram/routing/curves.js +120 -0
- package/dist/diagram/routing/curves.js.map +1 -0
- package/dist/diagram/routing/grid.d.ts +15 -0
- package/dist/diagram/routing/grid.d.ts.map +1 -0
- package/dist/diagram/routing/grid.js +245 -0
- package/dist/diagram/routing/grid.js.map +1 -0
- package/dist/diagram/routing/index.d.ts +10 -0
- package/dist/diagram/routing/index.d.ts.map +1 -0
- package/dist/diagram/routing/index.js +10 -0
- package/dist/diagram/routing/index.js.map +1 -0
- package/dist/diagram/routing/legacy.d.ts +6 -0
- package/dist/diagram/routing/legacy.d.ts.map +1 -0
- package/dist/diagram/routing/legacy.js +27 -0
- package/dist/diagram/routing/legacy.js.map +1 -0
- package/dist/diagram/routing/nudge.d.ts +4 -0
- package/dist/diagram/routing/nudge.d.ts.map +1 -0
- package/dist/diagram/routing/nudge.js +96 -0
- package/dist/diagram/routing/nudge.js.map +1 -0
- package/dist/diagram/routing/orthogonal.d.ts +6 -0
- package/dist/diagram/routing/orthogonal.d.ts.map +1 -0
- package/dist/diagram/routing/orthogonal.js +56 -0
- package/dist/diagram/routing/orthogonal.js.map +1 -0
- package/dist/diagram/routing/path.d.ts +16 -0
- package/dist/diagram/routing/path.d.ts.map +1 -0
- package/dist/diagram/routing/path.js +128 -0
- package/dist/diagram/routing/path.js.map +1 -0
- package/dist/diagram/routing/sides.d.ts +22 -0
- package/dist/diagram/routing/sides.d.ts.map +1 -0
- package/dist/diagram/routing/sides.js +74 -0
- package/dist/diagram/routing/sides.js.map +1 -0
- package/dist/diagram/routing/types.d.ts +46 -0
- package/dist/diagram/routing/types.d.ts.map +1 -0
- package/dist/diagram/routing/types.js +11 -0
- package/dist/diagram/routing/types.js.map +1 -0
- package/dist/diagram/schema.d.ts +70 -37
- package/dist/diagram/schema.d.ts.map +1 -1
- package/dist/diagram/schema.js +62 -8
- package/dist/diagram/schema.js.map +1 -1
- package/dist/diagram/styleInput.d.ts +41 -0
- package/dist/diagram/styleInput.d.ts.map +1 -0
- package/dist/diagram/styleInput.js +105 -0
- package/dist/diagram/styleInput.js.map +1 -0
- package/dist/diagram/views.d.ts +9 -0
- package/dist/diagram/views.d.ts.map +1 -0
- package/dist/diagram/views.js +42 -0
- package/dist/diagram/views.js.map +1 -0
- package/dist/http-file/flatImport.d.ts +1 -0
- package/dist/http-file/flatImport.d.ts.map +1 -1
- package/dist/http-file/flatImport.js.map +1 -1
- package/dist/http-file/index.d.ts +1 -1
- package/dist/http-file/index.d.ts.map +1 -1
- package/dist/http-file/index.js +1 -1
- package/dist/http-file/index.js.map +1 -1
- package/dist/http-file/parseHttpFile.d.ts +1 -0
- package/dist/http-file/parseHttpFile.d.ts.map +1 -1
- package/dist/http-file/parseHttpFile.js +40 -8
- package/dist/http-file/parseHttpFile.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/request/requestBody.d.ts +55 -0
- package/dist/request/requestBody.d.ts.map +1 -0
- package/dist/request/requestBody.js +198 -0
- package/dist/request/requestBody.js.map +1 -0
- package/dist/runner/requestRunner.d.ts.map +1 -1
- package/dist/runner/requestRunner.js +127 -43
- package/dist/runner/requestRunner.js.map +1 -1
- package/dist/runner/runCollection.d.ts +1 -1
- package/dist/runner/runCollection.d.ts.map +1 -1
- package/dist/runner/runCollection.js +9 -2
- package/dist/runner/runCollection.js.map +1 -1
- package/dist/scenario-runner/adapters/helpers/request-helpers.d.ts +2 -1
- package/dist/scenario-runner/adapters/helpers/request-helpers.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/helpers/request-helpers.js +8 -40
- package/dist/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
- package/dist/scenario-runner/adapters/request-executor.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/request-executor.js +2 -29
- package/dist/scenario-runner/adapters/request-executor.js.map +1 -1
- package/dist/scenario-runner/index.d.ts +1 -0
- package/dist/scenario-runner/index.d.ts.map +1 -1
- package/dist/scenario-runner/index.js +1 -0
- package/dist/scenario-runner/index.js.map +1 -1
- package/dist/scenario-runner/labels.d.ts +3 -0
- package/dist/scenario-runner/labels.d.ts.map +1 -0
- package/dist/scenario-runner/labels.js +20 -0
- package/dist/scenario-runner/labels.js.map +1 -0
- package/dist/scenario-runner/runScenario.d.ts.map +1 -1
- package/dist/scenario-runner/runScenario.js +4 -11
- package/dist/scenario-runner/runScenario.js.map +1 -1
- package/dist/script/legacy.d.ts.map +1 -1
- package/dist/script/legacy.js +24 -9
- package/dist/script/legacy.js.map +1 -1
- package/dist/types/index.d.ts +18 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/variables/resolver.d.ts.map +1 -1
- package/dist/variables/resolver.js +27 -5
- package/dist/variables/resolver.js.map +1 -1
- package/dist-cjs/ai/index.js +4 -1
- package/dist-cjs/ai/index.js.map +1 -1
- package/dist-cjs/ai/outputSchemas.js +1 -0
- package/dist-cjs/ai/outputSchemas.js.map +1 -1
- package/dist-cjs/ai/parse/diagram.js +6 -3
- package/dist-cjs/ai/parse/diagram.js.map +1 -1
- package/dist-cjs/ai/prompts/diagram.js +44 -5
- package/dist-cjs/ai/prompts/diagram.js.map +1 -1
- package/dist-cjs/ai/prompts/scenario.js +9 -8
- package/dist-cjs/ai/prompts/scenario.js.map +1 -1
- package/dist-cjs/ai/prompts/scenarioNodeReference.js +886 -0
- package/dist-cjs/ai/prompts/scenarioNodeReference.js.map +1 -0
- package/dist-cjs/diagram/bpmn/derive.js +398 -0
- package/dist-cjs/diagram/bpmn/derive.js.map +1 -0
- package/dist-cjs/diagram/bpmn/index.js +31 -0
- package/dist-cjs/diagram/bpmn/index.js.map +1 -0
- package/dist-cjs/diagram/bpmn/patch.js +774 -0
- package/dist-cjs/diagram/bpmn/patch.js.map +1 -0
- package/dist-cjs/diagram/bpmn/place.js +69 -0
- package/dist-cjs/diagram/bpmn/place.js.map +1 -0
- package/dist-cjs/diagram/bpmn/serialize.js +299 -0
- package/dist-cjs/diagram/bpmn/serialize.js.map +1 -0
- package/dist-cjs/diagram/bpmn/types.js +99 -0
- package/dist-cjs/diagram/bpmn/types.js.map +1 -0
- package/dist-cjs/diagram/bpmn/xml.js +367 -0
- package/dist-cjs/diagram/bpmn/xml.js.map +1 -0
- package/dist-cjs/diagram/elkGraph.js +1 -0
- package/dist-cjs/diagram/elkGraph.js.map +1 -1
- package/dist-cjs/diagram/fixtures.js +117 -0
- package/dist-cjs/diagram/fixtures.js.map +1 -0
- package/dist-cjs/diagram/geometry.js +143 -0
- package/dist-cjs/diagram/geometry.js.map +1 -0
- package/dist-cjs/diagram/history.js +65 -0
- package/dist-cjs/diagram/history.js.map +1 -0
- package/dist-cjs/diagram/ids.js +58 -0
- package/dist-cjs/diagram/ids.js.map +1 -0
- package/dist-cjs/diagram/index.js +23 -8
- package/dist-cjs/diagram/index.js.map +1 -1
- package/dist-cjs/diagram/layout.js +364 -0
- package/dist-cjs/diagram/layout.js.map +1 -0
- package/dist-cjs/diagram/mermaid/derive.js +353 -0
- package/dist-cjs/diagram/mermaid/derive.js.map +1 -0
- package/dist-cjs/diagram/mermaid/index.js +44 -0
- package/dist-cjs/diagram/mermaid/index.js.map +1 -0
- package/dist-cjs/diagram/mermaid/parse.js +681 -0
- package/dist-cjs/diagram/mermaid/parse.js.map +1 -0
- package/dist-cjs/diagram/mermaid/patch.js +468 -0
- package/dist-cjs/diagram/mermaid/patch.js.map +1 -0
- package/dist-cjs/diagram/mermaid/serialize.js +133 -0
- package/dist-cjs/diagram/mermaid/serialize.js.map +1 -0
- package/dist-cjs/diagram/mermaid/shapes.js +327 -0
- package/dist-cjs/diagram/mermaid/shapes.js.map +1 -0
- package/dist-cjs/diagram/migrations.js +9 -3
- package/dist-cjs/diagram/migrations.js.map +1 -1
- package/dist-cjs/diagram/normalize.js +198 -1
- package/dist-cjs/diagram/normalize.js.map +1 -1
- package/dist-cjs/diagram/ops.js +829 -0
- package/dist-cjs/diagram/ops.js.map +1 -0
- package/dist-cjs/diagram/opsReference.js +55 -0
- package/dist-cjs/diagram/opsReference.js.map +1 -0
- package/dist-cjs/diagram/patchInput.js +636 -0
- package/dist-cjs/diagram/patchInput.js.map +1 -0
- package/dist-cjs/diagram/routing/batch.js +142 -0
- package/dist-cjs/diagram/routing/batch.js.map +1 -0
- package/dist-cjs/diagram/routing/curves.js +127 -0
- package/dist-cjs/diagram/routing/curves.js.map +1 -0
- package/dist-cjs/diagram/routing/grid.js +250 -0
- package/dist-cjs/diagram/routing/grid.js.map +1 -0
- package/dist-cjs/diagram/routing/index.js +29 -0
- package/dist-cjs/diagram/routing/index.js.map +1 -0
- package/dist-cjs/diagram/routing/legacy.js +32 -0
- package/dist-cjs/diagram/routing/legacy.js.map +1 -0
- package/dist-cjs/diagram/routing/nudge.js +100 -0
- package/dist-cjs/diagram/routing/nudge.js.map +1 -0
- package/dist-cjs/diagram/routing/orthogonal.js +62 -0
- package/dist-cjs/diagram/routing/orthogonal.js.map +1 -0
- package/dist-cjs/diagram/routing/path.js +137 -0
- package/dist-cjs/diagram/routing/path.js.map +1 -0
- package/dist-cjs/diagram/routing/sides.js +82 -0
- package/dist-cjs/diagram/routing/sides.js.map +1 -0
- package/dist-cjs/diagram/routing/types.js +15 -0
- package/dist-cjs/diagram/routing/types.js.map +1 -0
- package/dist-cjs/diagram/schema.js +64 -9
- package/dist-cjs/diagram/schema.js.map +1 -1
- package/dist-cjs/diagram/styleInput.js +113 -0
- package/dist-cjs/diagram/styleInput.js.map +1 -0
- package/dist-cjs/diagram/views.js +46 -0
- package/dist-cjs/diagram/views.js.map +1 -0
- package/dist-cjs/http-file/flatImport.js.map +1 -1
- package/dist-cjs/http-file/index.js +2 -1
- package/dist-cjs/http-file/index.js.map +1 -1
- package/dist-cjs/http-file/parseHttpFile.js +42 -9
- package/dist-cjs/http-file/parseHttpFile.js.map +1 -1
- package/dist-cjs/index.js +9 -1
- package/dist-cjs/index.js.map +1 -1
- package/dist-cjs/request/requestBody.js +206 -0
- package/dist-cjs/request/requestBody.js.map +1 -0
- package/dist-cjs/runner/requestRunner.js +127 -43
- package/dist-cjs/runner/requestRunner.js.map +1 -1
- package/dist-cjs/runner/runCollection.js +9 -2
- package/dist-cjs/runner/runCollection.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js +7 -42
- package/dist-cjs/scenario-runner/adapters/helpers/request-helpers.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/request-executor.js +3 -30
- package/dist-cjs/scenario-runner/adapters/request-executor.js.map +1 -1
- package/dist-cjs/scenario-runner/index.js +4 -1
- package/dist-cjs/scenario-runner/index.js.map +1 -1
- package/dist-cjs/scenario-runner/labels.js +24 -0
- package/dist-cjs/scenario-runner/labels.js.map +1 -0
- package/dist-cjs/scenario-runner/runScenario.js +4 -11
- package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
- package/dist-cjs/script/legacy.js +24 -9
- package/dist-cjs/script/legacy.js.map +1 -1
- package/dist-cjs/variables/resolver.js +27 -5
- package/dist-cjs/variables/resolver.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
// The AUTHORING vocabulary — the compact shape AI models and MCP clients
|
|
2
|
+
// write (`parent` not `parentId`, a `semantic` instead of a dash tuple, CSS
|
|
3
|
+
// colors, optional positions) — lowered onto the canonical ops of ops.ts.
|
|
4
|
+
// One implementation of the merge rules, type checks and suggestions, shared
|
|
5
|
+
// by the MCP server (behind its zod schemas), the AI EDIT mode and imports.
|
|
6
|
+
// Moved from the MCP server's build.ts / patch.ts.
|
|
7
|
+
import { BPMN_CONTAINER_TYPES, bpmnElementForType, getBpmnType, isBpmnContainerType, suggestBpmnTypes } from "./bpmn/types.js";
|
|
8
|
+
import { DIAGRAM_COMPONENT_CATALOG, getDiagramComponent, searchDiagramComponents } from "./componentCatalog.js";
|
|
9
|
+
import { isBusComponentType, isDefaultEdgeStyle, semanticStyle } from "./edgeSemantics.js";
|
|
10
|
+
import { absolutePosition, absoluteRect, boundingBox, indexNodes, toParentRelative } from "./geometry.js";
|
|
11
|
+
import { sequentialIdFactory, slugIdFactory } from "./ids.js";
|
|
12
|
+
import { normalizeDiagramStyles } from "./normalize.js";
|
|
13
|
+
import { DiagramOpError } from "./ops.js";
|
|
14
|
+
import { nowIso, safeParseDiagramDocument, SCHEMA_VERSION, } from "./schema.js";
|
|
15
|
+
import { applyStyleInput, styleInputToChanges, toTokenRef } from "./styleInput.js";
|
|
16
|
+
// --- catalog helpers ---------------------------------------------------------------
|
|
17
|
+
export const suggestComponentTypes = (type, limit = 5) => {
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
const out = [];
|
|
20
|
+
const push = (candidate) => {
|
|
21
|
+
if (!seen.has(candidate) && out.length < limit) {
|
|
22
|
+
seen.add(candidate);
|
|
23
|
+
out.push(candidate);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
for (const entry of searchDiagramComponents(type))
|
|
27
|
+
push(entry.type);
|
|
28
|
+
// A typo usually sits at the END of the last segment ("db.postgresqlx"):
|
|
29
|
+
// shrink it until something in the catalog contains it.
|
|
30
|
+
const tail = (type.split(".").pop() ?? type).toLowerCase();
|
|
31
|
+
for (let length = tail.length; length >= 3 && out.length < limit; length -= 1) {
|
|
32
|
+
const needle = tail.slice(0, length);
|
|
33
|
+
for (const entry of DIAGRAM_COMPONENT_CATALOG) {
|
|
34
|
+
const haystack = [entry.type, entry.label, ...(entry.aliases ?? [])].join(" ").toLowerCase();
|
|
35
|
+
if (haystack.includes(needle))
|
|
36
|
+
push(entry.type);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Still nothing: offer the same namespace ("db.*", "aws.*").
|
|
40
|
+
const namespace = type.includes(".") ? `${type.split(".")[0].toLowerCase()}.` : null;
|
|
41
|
+
if (namespace) {
|
|
42
|
+
for (const entry of DIAGRAM_COMPONENT_CATALOG)
|
|
43
|
+
if (entry.type.toLowerCase().startsWith(namespace))
|
|
44
|
+
push(entry.type);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
};
|
|
48
|
+
// Containers hold children through `parent`: frames and swimlane pools.
|
|
49
|
+
export const isContainerType = (type) => {
|
|
50
|
+
const def = type ? getDiagramComponent(type) : undefined;
|
|
51
|
+
return Boolean(def?.container || def?.swimlane);
|
|
52
|
+
};
|
|
53
|
+
export const containerTypes = () => DIAGRAM_COMPONENT_CATALOG.filter((entry) => entry.container || entry.swimlane).map((entry) => entry.type);
|
|
54
|
+
export const parentWarning = (id, parentId, parentType, kind = "canvas") => {
|
|
55
|
+
if (kind === "bpmn") {
|
|
56
|
+
return isBpmnContainerType(parentType)
|
|
57
|
+
? null
|
|
58
|
+
: `Node ${id} sits inside "${parentId}" (${parentType ?? "unknown"}), which is not a BPMN container — use ${BPMN_CONTAINER_TYPES.join(", ")}.`;
|
|
59
|
+
}
|
|
60
|
+
return isContainerType(parentType)
|
|
61
|
+
? null
|
|
62
|
+
: `Node ${id} sits inside "${parentId}" (${parentType ?? "unknown"}), which is not a container — use ${containerTypes().join(", ")} to group nodes.`;
|
|
63
|
+
};
|
|
64
|
+
// line / arrow / line.dashed are palette connectors (0-height), not nodes.
|
|
65
|
+
export const assertNotConnector = (type, id, opIndex = -1) => {
|
|
66
|
+
if (getDiagramComponent(type)?.variant === "line") {
|
|
67
|
+
throw new DiagramOpError("DIAGRAM_CONNECTOR_NOT_NODE", `"${type}" on ${id} is a connector, not a node — draw it as an edge between two nodes.`, opIndex);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
// Edge input → the stored edge's visual fields: the semantic's line tuple
|
|
71
|
+
// plus an optional color.
|
|
72
|
+
export const edgeStyleFor = (semantic, color) => {
|
|
73
|
+
const base = semantic ? semanticStyle(semantic) : undefined;
|
|
74
|
+
if (!color)
|
|
75
|
+
return base;
|
|
76
|
+
return { ...(base ?? {}), stroke: toTokenRef(color) };
|
|
77
|
+
};
|
|
78
|
+
const resolveType = (type, id, strict, warnings, opIndex, kind = "canvas") => {
|
|
79
|
+
const trimmed = type.trim();
|
|
80
|
+
if (kind === "bpmn") {
|
|
81
|
+
if (getBpmnType(trimmed))
|
|
82
|
+
return { type: trimmed, known: true };
|
|
83
|
+
const suggestions = suggestBpmnTypes(trimmed);
|
|
84
|
+
if (strict) {
|
|
85
|
+
throw new DiagramOpError("DIAGRAM_UNKNOWN_COMPONENT_TYPE", `Unknown BPMN type "${trimmed}" on ${id}. Use one of the bpmn.* types (e.g. ${suggestions.join(", ") || "bpmn.user-task, bpmn.exclusive-gateway"}), or pass strictTypes:false to draw it as a task.`, opIndex, { unknownTypes: [{ type: trimmed, suggestions }] });
|
|
86
|
+
}
|
|
87
|
+
warnings.push(`Unknown BPMN type "${trimmed}" on ${id} → drawn as a task (suggestions: ${suggestions.join(", ") || "none"})`);
|
|
88
|
+
return { type: "bpmn.task", known: false };
|
|
89
|
+
}
|
|
90
|
+
if (getDiagramComponent(trimmed)) {
|
|
91
|
+
assertNotConnector(trimmed, id, opIndex);
|
|
92
|
+
return { type: trimmed, known: true };
|
|
93
|
+
}
|
|
94
|
+
const suggestions = suggestComponentTypes(trimmed);
|
|
95
|
+
if (strict) {
|
|
96
|
+
throw new DiagramOpError("DIAGRAM_UNKNOWN_COMPONENT_TYPE", `Unknown component type "${trimmed}" on ${id}. Read callman://diagram/components for the catalog, or pass strictTypes:false to draw it as a rectangle.`, opIndex, { unknownTypes: [{ type: trimmed, suggestions }] });
|
|
97
|
+
}
|
|
98
|
+
warnings.push(`Unknown component type "${trimmed}" on ${id} → drawn as a rectangle (suggestions: ${suggestions.join(", ") || "none"})`);
|
|
99
|
+
return { type: "rect", known: false };
|
|
100
|
+
};
|
|
101
|
+
const nodeFromInput = (input, id, strict, warnings, opIndex, kind = "canvas") => {
|
|
102
|
+
const { type, known } = resolveType(input.type, id, strict, warnings, opIndex, kind);
|
|
103
|
+
const def = kind === "bpmn" ? undefined : getDiagramComponent(type);
|
|
104
|
+
const bpmn = kind === "bpmn" ? getBpmnType(type) : undefined;
|
|
105
|
+
const label = input.label !== undefined || !known ? (input.label ?? input.type) : def?.defaultLabel;
|
|
106
|
+
// A BPMN node always knows which element it stands for.
|
|
107
|
+
const data = kind === "bpmn"
|
|
108
|
+
? { ...(input.data ?? {}), bpmn: { ...(input.data?.bpmn ?? {}), element: bpmnElementForType(type) ?? "task" } }
|
|
109
|
+
: input.data;
|
|
110
|
+
const node = {
|
|
111
|
+
id,
|
|
112
|
+
type,
|
|
113
|
+
position: input.position ?? { x: 0, y: 0 },
|
|
114
|
+
size: input.size ?? bpmn?.size ?? def?.defaultSize ?? { width: 140, height: 60 },
|
|
115
|
+
...(label !== undefined ? { label } : {}),
|
|
116
|
+
...(input.parent ? { parentId: input.parent } : {}),
|
|
117
|
+
...(input.style ? { style: applyStyleInput(undefined, input.style) } : {}),
|
|
118
|
+
...(input.rotation ? { rotation: input.rotation } : {}),
|
|
119
|
+
...(input.locked ? { locked: true } : {}),
|
|
120
|
+
...(input.hidden ? { hidden: true } : {}),
|
|
121
|
+
...(input.collapsed ? { collapsed: true } : {}),
|
|
122
|
+
...(data ? { data } : {}),
|
|
123
|
+
};
|
|
124
|
+
if (node.style === undefined)
|
|
125
|
+
delete node.style;
|
|
126
|
+
return node;
|
|
127
|
+
};
|
|
128
|
+
const edgeFromInput = (input, id, typeOf, kind = "canvas") => {
|
|
129
|
+
let semantic = input.semantic;
|
|
130
|
+
// A fresh edge into a broker reads as a bus line — the studio's own rule.
|
|
131
|
+
if (kind === "canvas" && !semantic && (isBusComponentType(typeOf(input.source)) || isBusComponentType(typeOf(input.target))))
|
|
132
|
+
semantic = "bus";
|
|
133
|
+
// A BPMN flow is a sequence flow unless said otherwise.
|
|
134
|
+
const data = kind === "bpmn"
|
|
135
|
+
? { ...(input.data ?? {}), bpmn: { flow: "sequence", ...(input.data?.bpmn ?? {}) } }
|
|
136
|
+
: input.data;
|
|
137
|
+
const style = edgeStyleFor(semantic, input.color);
|
|
138
|
+
return {
|
|
139
|
+
id,
|
|
140
|
+
source: input.source,
|
|
141
|
+
target: input.target,
|
|
142
|
+
...(input.label ? { label: input.label } : {}),
|
|
143
|
+
...(input.routing ? { routing: input.routing } : {}),
|
|
144
|
+
markerEnd: input.markerEnd ?? "classicThin",
|
|
145
|
+
...(input.markerStart && input.markerStart !== "none" ? { markerStart: input.markerStart } : {}),
|
|
146
|
+
...(input.sourceSide ? { sourceHandle: input.sourceSide } : {}),
|
|
147
|
+
...(input.targetSide ? { targetHandle: input.targetSide } : {}),
|
|
148
|
+
...(style && (!isDefaultEdgeStyle(style) || semantic === "sync" || input.color) ? { style } : {}),
|
|
149
|
+
...(data ? { data } : {}),
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
// --- lowering -----------------------------------------------------------------------
|
|
153
|
+
export const lowerPatchOps = (doc, input, options = {}) => {
|
|
154
|
+
const strict = options.strictTypes !== false;
|
|
155
|
+
const kind = options.kind ?? (doc.source?.format === "bpmn" ? "bpmn" : "canvas");
|
|
156
|
+
const warnings = [];
|
|
157
|
+
const unpositionedNodeIds = [];
|
|
158
|
+
const idMap = {};
|
|
159
|
+
const ops = [];
|
|
160
|
+
let relayout;
|
|
161
|
+
// A working view of ids/types/parents so later ops in the same batch can
|
|
162
|
+
// reference nodes added earlier — the ops themselves stay canonical.
|
|
163
|
+
const nodes = new Map(doc.nodes.map((node) => [node.id, { ...node }]));
|
|
164
|
+
const edges = new Map(doc.edges.map((edge) => [edge.id, { ...edge }]));
|
|
165
|
+
const takenIds = () => [...nodes.keys(), ...edges.keys()];
|
|
166
|
+
const genNodeId = options.genNodeId ?? slugIdFactory(takenIds(), "n");
|
|
167
|
+
const genEdgeId = options.genEdgeId ?? sequentialIdFactory("e", takenIds());
|
|
168
|
+
const typeOf = (id) => nodes.get(id)?.type;
|
|
169
|
+
const mapId = (id) => idMap[id] ?? id;
|
|
170
|
+
const requireNode = (id, index) => {
|
|
171
|
+
const node = nodes.get(mapId(id));
|
|
172
|
+
if (!node)
|
|
173
|
+
throw new DiagramOpError("DIAGRAM_NODE_NOT_FOUND", `Node "${id}" does not exist.`, index);
|
|
174
|
+
return node;
|
|
175
|
+
};
|
|
176
|
+
input.forEach((op, index) => {
|
|
177
|
+
switch (op.op) {
|
|
178
|
+
case "setTitle":
|
|
179
|
+
ops.push({ op: "setTitle", title: op.title });
|
|
180
|
+
return;
|
|
181
|
+
case "addNode": {
|
|
182
|
+
const requested = op.node.id?.trim();
|
|
183
|
+
let id = requested || genNodeId(op.node.label ?? op.node.type);
|
|
184
|
+
if (requested && (nodes.has(requested) || edges.has(requested))) {
|
|
185
|
+
throw new DiagramOpError("DIAGRAM_DUPLICATE_NODE_ID", `A node with id "${requested}" already exists.`, index);
|
|
186
|
+
}
|
|
187
|
+
if (!requested && (nodes.has(id) || edges.has(id)))
|
|
188
|
+
id = genNodeId();
|
|
189
|
+
const parent = op.node.parent ? mapId(op.node.parent) : undefined;
|
|
190
|
+
if (parent && !nodes.has(parent))
|
|
191
|
+
throw new DiagramOpError("DIAGRAM_PARENT_NOT_FOUND", `Parent "${op.node.parent}" does not exist.`, index);
|
|
192
|
+
const node = nodeFromInput({ ...op.node, parent }, id, strict, warnings, index, kind);
|
|
193
|
+
if (parent) {
|
|
194
|
+
const warning = parentWarning(id, parent, typeOf(parent), kind);
|
|
195
|
+
if (warning)
|
|
196
|
+
warnings.push(warning);
|
|
197
|
+
}
|
|
198
|
+
nodes.set(id, node);
|
|
199
|
+
if (requested && requested !== id)
|
|
200
|
+
idMap[requested] = id;
|
|
201
|
+
if (!op.node.position)
|
|
202
|
+
unpositionedNodeIds.push(id);
|
|
203
|
+
ops.push({ op: "addNode", node });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
case "updateNode": {
|
|
207
|
+
const id = mapId(op.id);
|
|
208
|
+
const node = requireNode(id, index);
|
|
209
|
+
const changes = op.changes;
|
|
210
|
+
const nodeChanges = {};
|
|
211
|
+
if (changes.type !== undefined)
|
|
212
|
+
nodeChanges.type = resolveType(changes.type, id, strict, warnings, index, kind).type;
|
|
213
|
+
if (changes.label !== undefined)
|
|
214
|
+
nodeChanges.label = changes.label;
|
|
215
|
+
if (changes.rotation !== undefined)
|
|
216
|
+
nodeChanges.rotation = changes.rotation;
|
|
217
|
+
if (changes.locked !== undefined)
|
|
218
|
+
nodeChanges.locked = changes.locked;
|
|
219
|
+
if (changes.hidden !== undefined)
|
|
220
|
+
nodeChanges.hidden = changes.hidden;
|
|
221
|
+
if (changes.collapsed !== undefined)
|
|
222
|
+
nodeChanges.collapsed = changes.collapsed;
|
|
223
|
+
if (changes.style !== undefined)
|
|
224
|
+
nodeChanges.style = changes.style === null ? null : styleInputToChanges(node.style, changes.style);
|
|
225
|
+
if (changes.data !== undefined)
|
|
226
|
+
nodeChanges.data = changes.data;
|
|
227
|
+
if (Object.keys(nodeChanges).length > 0) {
|
|
228
|
+
ops.push({ op: "updateNode", id, changes: nodeChanges });
|
|
229
|
+
const next = { ...node };
|
|
230
|
+
if (nodeChanges.type)
|
|
231
|
+
next.type = nodeChanges.type;
|
|
232
|
+
nodes.set(id, next);
|
|
233
|
+
}
|
|
234
|
+
if (changes.size !== undefined) {
|
|
235
|
+
ops.push({ op: "resizeNode", id, size: changes.size });
|
|
236
|
+
nodes.set(id, { ...nodes.get(id), size: changes.size });
|
|
237
|
+
}
|
|
238
|
+
if (changes.parent !== undefined) {
|
|
239
|
+
const parent = changes.parent ? mapId(changes.parent) : null;
|
|
240
|
+
if (parent && !nodes.has(parent))
|
|
241
|
+
throw new DiagramOpError("DIAGRAM_PARENT_NOT_FOUND", `Parent "${changes.parent}" does not exist.`, index);
|
|
242
|
+
if (parent === id)
|
|
243
|
+
throw new DiagramOpError("DIAGRAM_PARENT_CYCLE", `Node "${id}" cannot contain itself.`, index);
|
|
244
|
+
const byId = indexNodes([...nodes.values()]);
|
|
245
|
+
const position = changes.position ?? toParentRelative(absolutePosition(nodes.get(id), byId), parent, byId);
|
|
246
|
+
ops.push({ op: "reparent", id, parentId: parent, position });
|
|
247
|
+
if (parent) {
|
|
248
|
+
const warning = parentWarning(id, parent, typeOf(parent), kind);
|
|
249
|
+
if (warning)
|
|
250
|
+
warnings.push(warning);
|
|
251
|
+
}
|
|
252
|
+
const next = { ...nodes.get(id), position };
|
|
253
|
+
if (parent)
|
|
254
|
+
next.parentId = parent;
|
|
255
|
+
else
|
|
256
|
+
delete next.parentId;
|
|
257
|
+
nodes.set(id, next);
|
|
258
|
+
}
|
|
259
|
+
else if (changes.position !== undefined) {
|
|
260
|
+
ops.push({ op: "moveNodes", moves: [{ id, position: changes.position }] });
|
|
261
|
+
nodes.set(id, { ...nodes.get(id), position: changes.position });
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
case "removeNode": {
|
|
266
|
+
const id = mapId(op.id);
|
|
267
|
+
requireNode(id, index);
|
|
268
|
+
ops.push({ op: "removeNode", id, children: "lift" });
|
|
269
|
+
nodes.delete(id);
|
|
270
|
+
for (const [edgeId, edge] of [...edges])
|
|
271
|
+
if (edge.source === id || edge.target === id)
|
|
272
|
+
edges.delete(edgeId);
|
|
273
|
+
for (const node of nodes.values())
|
|
274
|
+
if (node.parentId === id)
|
|
275
|
+
delete node.parentId;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
case "addEdge": {
|
|
279
|
+
const requested = op.edge.id?.trim();
|
|
280
|
+
let id = requested || genEdgeId();
|
|
281
|
+
if (requested && (edges.has(requested) || nodes.has(requested))) {
|
|
282
|
+
throw new DiagramOpError("DIAGRAM_DUPLICATE_EDGE_ID", `An edge with id "${requested}" already exists.`, index);
|
|
283
|
+
}
|
|
284
|
+
if (!requested && (edges.has(id) || nodes.has(id)))
|
|
285
|
+
id = genEdgeId();
|
|
286
|
+
const source = mapId(op.edge.source);
|
|
287
|
+
const target = mapId(op.edge.target);
|
|
288
|
+
for (const endpoint of [source, target]) {
|
|
289
|
+
if (!nodes.has(endpoint))
|
|
290
|
+
throw new DiagramOpError("DIAGRAM_EDGE_ENDPOINT_NOT_FOUND", `Edge endpoint "${endpoint}" does not exist.`, index);
|
|
291
|
+
}
|
|
292
|
+
const edge = edgeFromInput({ ...op.edge, source, target }, id, typeOf, kind);
|
|
293
|
+
edges.set(id, edge);
|
|
294
|
+
ops.push({ op: "addEdge", edge });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
case "updateEdge": {
|
|
298
|
+
const id = mapId(op.id);
|
|
299
|
+
const edge = edges.get(id);
|
|
300
|
+
if (!edge)
|
|
301
|
+
throw new DiagramOpError("DIAGRAM_EDGE_NOT_FOUND", `Edge "${op.id}" does not exist.`, index);
|
|
302
|
+
const changes = op.changes;
|
|
303
|
+
const edgeChanges = {};
|
|
304
|
+
if (changes.source !== undefined)
|
|
305
|
+
edgeChanges.source = mapId(changes.source);
|
|
306
|
+
if (changes.target !== undefined)
|
|
307
|
+
edgeChanges.target = mapId(changes.target);
|
|
308
|
+
if (changes.label !== undefined)
|
|
309
|
+
edgeChanges.label = changes.label === "" ? null : changes.label;
|
|
310
|
+
if (changes.routing !== undefined)
|
|
311
|
+
edgeChanges.routing = changes.routing;
|
|
312
|
+
if (changes.markerEnd !== undefined)
|
|
313
|
+
edgeChanges.markerEnd = changes.markerEnd;
|
|
314
|
+
if (changes.markerStart !== undefined)
|
|
315
|
+
edgeChanges.markerStart = changes.markerStart === "none" ? null : changes.markerStart;
|
|
316
|
+
if (changes.sourceSide !== undefined)
|
|
317
|
+
edgeChanges.sourceHandle = changes.sourceSide;
|
|
318
|
+
if (changes.targetSide !== undefined)
|
|
319
|
+
edgeChanges.targetHandle = changes.targetSide;
|
|
320
|
+
if (changes.data !== undefined)
|
|
321
|
+
edgeChanges.data = changes.data;
|
|
322
|
+
// The semantic owns the line tuple (dash + width); the color is kept
|
|
323
|
+
// unless it is changed explicitly.
|
|
324
|
+
if (changes.semantic !== undefined || changes.color !== undefined) {
|
|
325
|
+
const style = {};
|
|
326
|
+
if (changes.semantic !== undefined) {
|
|
327
|
+
const line = semanticStyle(changes.semantic);
|
|
328
|
+
style.dashed = line.dashed ?? null;
|
|
329
|
+
style.dashPattern = line.dashPattern ?? null;
|
|
330
|
+
style.strokeWidth = line.strokeWidth ?? null;
|
|
331
|
+
}
|
|
332
|
+
if (changes.color !== undefined)
|
|
333
|
+
style.stroke = changes.color === null ? null : toTokenRef(changes.color);
|
|
334
|
+
edgeChanges.style = style;
|
|
335
|
+
}
|
|
336
|
+
for (const endpoint of [edgeChanges.source, edgeChanges.target]) {
|
|
337
|
+
if (endpoint !== undefined && !nodes.has(endpoint)) {
|
|
338
|
+
throw new DiagramOpError("DIAGRAM_EDGE_ENDPOINT_NOT_FOUND", `Edge endpoint "${endpoint}" does not exist.`, index);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (Object.keys(edgeChanges).length > 0) {
|
|
342
|
+
ops.push({ op: "updateEdge", id, changes: edgeChanges });
|
|
343
|
+
edges.set(id, { ...edge, ...(edgeChanges.source ? { source: edgeChanges.source } : {}), ...(edgeChanges.target ? { target: edgeChanges.target } : {}) });
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
case "removeEdge": {
|
|
348
|
+
const id = mapId(op.id);
|
|
349
|
+
if (!edges.has(id))
|
|
350
|
+
throw new DiagramOpError("DIAGRAM_EDGE_NOT_FOUND", `Edge "${op.id}" does not exist.`, index);
|
|
351
|
+
edges.delete(id);
|
|
352
|
+
ops.push({ op: "removeEdge", id });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
case "moveNode": {
|
|
356
|
+
const id = mapId(op.id);
|
|
357
|
+
requireNode(id, index);
|
|
358
|
+
ops.push({ op: "moveNodes", moves: [{ id, position: op.position }] });
|
|
359
|
+
nodes.set(id, { ...nodes.get(id), position: op.position });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
case "reparent": {
|
|
363
|
+
const id = mapId(op.id);
|
|
364
|
+
const node = requireNode(id, index);
|
|
365
|
+
const parent = op.parent ? mapId(op.parent) : null;
|
|
366
|
+
if (parent && !nodes.has(parent))
|
|
367
|
+
throw new DiagramOpError("DIAGRAM_PARENT_NOT_FOUND", `Parent "${op.parent}" does not exist.`, index);
|
|
368
|
+
const byId = indexNodes([...nodes.values()]);
|
|
369
|
+
const position = op.position ?? toParentRelative(absolutePosition(node, byId), parent, byId);
|
|
370
|
+
ops.push({ op: "reparent", id, parentId: parent, position });
|
|
371
|
+
const next = { ...node, position };
|
|
372
|
+
if (parent)
|
|
373
|
+
next.parentId = parent;
|
|
374
|
+
else
|
|
375
|
+
delete next.parentId;
|
|
376
|
+
nodes.set(id, next);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
case "setEdgeRoute": {
|
|
380
|
+
const id = mapId(op.id);
|
|
381
|
+
if (!edges.has(id))
|
|
382
|
+
throw new DiagramOpError("DIAGRAM_EDGE_NOT_FOUND", `Edge "${op.id}" does not exist.`, index);
|
|
383
|
+
const changes = {};
|
|
384
|
+
if (op.routing !== undefined)
|
|
385
|
+
changes.routing = op.routing;
|
|
386
|
+
if (op.sourceSide !== undefined)
|
|
387
|
+
changes.sourceHandle = op.sourceSide;
|
|
388
|
+
if (op.targetSide !== undefined)
|
|
389
|
+
changes.targetHandle = op.targetSide;
|
|
390
|
+
if (op.waypoints !== undefined)
|
|
391
|
+
changes.waypoints = op.waypoints;
|
|
392
|
+
// "Reset routing" when nothing else is asked.
|
|
393
|
+
if (Object.keys(changes).length === 0) {
|
|
394
|
+
changes.waypoints = null;
|
|
395
|
+
changes.sourceAnchor = null;
|
|
396
|
+
changes.targetAnchor = null;
|
|
397
|
+
}
|
|
398
|
+
ops.push({ op: "updateEdge", id, changes });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
case "reorder": {
|
|
402
|
+
const id = mapId(op.id);
|
|
403
|
+
const node = requireNode(id, index);
|
|
404
|
+
const siblings = [...nodes.values()].filter((candidate) => (candidate.parentId ?? undefined) === (node.parentId ?? undefined));
|
|
405
|
+
const target = op.index === "front" ? siblings.length - 1 : op.index === "back" ? 0 : op.index;
|
|
406
|
+
ops.push({ op: "reorder", kind: "node", id, index: target });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
case "setCanvas":
|
|
410
|
+
ops.push({ op: "setCanvas", changes: op.canvas });
|
|
411
|
+
return;
|
|
412
|
+
case "group": {
|
|
413
|
+
const memberIds = op.ids.map(mapId);
|
|
414
|
+
for (const memberId of memberIds)
|
|
415
|
+
requireNode(memberId, index);
|
|
416
|
+
const type = op.type ?? "frame";
|
|
417
|
+
if (!isContainerType(type)) {
|
|
418
|
+
throw new DiagramOpError("DIAGRAM_INVALID_OP", `"${type}" is not a container type — use ${containerTypes().join(", ")}.`, index);
|
|
419
|
+
}
|
|
420
|
+
const requested = op.id?.trim();
|
|
421
|
+
const groupId = requested || genNodeId(op.label ?? "group");
|
|
422
|
+
if (nodes.has(groupId) || edges.has(groupId))
|
|
423
|
+
throw new DiagramOpError("DIAGRAM_DUPLICATE_NODE_ID", `A node with id "${groupId}" already exists.`, index);
|
|
424
|
+
const grouped = groupNodesOps({ ...doc, nodes: [...nodes.values()], edges: [...edges.values()] }, memberIds, { id: groupId, type, label: op.label });
|
|
425
|
+
ops.push(...grouped.ops);
|
|
426
|
+
nodes.set(groupId, grouped.container);
|
|
427
|
+
for (const [memberId, position] of Object.entries(grouped.memberPositions)) {
|
|
428
|
+
nodes.set(memberId, { ...nodes.get(memberId), parentId: groupId, position });
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
case "ungroup": {
|
|
433
|
+
const id = mapId(op.id);
|
|
434
|
+
requireNode(id, index);
|
|
435
|
+
ops.push({ op: "removeNode", id, children: "lift" });
|
|
436
|
+
nodes.delete(id);
|
|
437
|
+
for (const node of nodes.values())
|
|
438
|
+
if (node.parentId === id)
|
|
439
|
+
delete node.parentId;
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
case "relayout":
|
|
443
|
+
relayout = { scope: op.scope, ...(op.direction ? { direction: op.direction } : {}) };
|
|
444
|
+
return;
|
|
445
|
+
default:
|
|
446
|
+
throw new DiagramOpError("DIAGRAM_INVALID_OP", `Unknown op "${op.op}"`, index);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
return { ops, warnings, unpositionedNodeIds, ...(relayout ? { relayout } : {}), idMap };
|
|
450
|
+
};
|
|
451
|
+
export const buildDiagramFromInput = (input, options = {}) => {
|
|
452
|
+
const kind = options.kind ?? "canvas";
|
|
453
|
+
const strict = options.strictTypes !== false;
|
|
454
|
+
const warnings = [];
|
|
455
|
+
const explicitIds = input.nodes.map((node) => node.id?.trim()).filter((id) => Boolean(id));
|
|
456
|
+
const duplicates = explicitIds.filter((id, index) => explicitIds.indexOf(id) !== index);
|
|
457
|
+
if (duplicates.length > 0) {
|
|
458
|
+
throw new DiagramOpError("DIAGRAM_DUPLICATE_NODE_ID", `Duplicate node ids: ${[...new Set(duplicates)].join(", ")}`);
|
|
459
|
+
}
|
|
460
|
+
const genNodeId = options.idStrategy === "sequential" ? sequentialIdFactory("n", explicitIds) : slugIdFactory(explicitIds, "n");
|
|
461
|
+
const genEdgeId = sequentialIdFactory("e", (input.edges ?? []).map((edge) => edge.id).filter((id) => Boolean(id)));
|
|
462
|
+
// Ids first so parents/edges can reference nodes declared later.
|
|
463
|
+
const ids = input.nodes.map((node) => node.id?.trim() || genNodeId(node.label ?? node.type));
|
|
464
|
+
const idSet = new Set(ids);
|
|
465
|
+
const unknown = [];
|
|
466
|
+
const nodes = input.nodes.map((node, index) => {
|
|
467
|
+
const id = ids[index];
|
|
468
|
+
if (node.parent && !idSet.has(node.parent)) {
|
|
469
|
+
throw new DiagramOpError("DIAGRAM_PARENT_NOT_FOUND", `Node ${id} references parent "${node.parent}" which is not in nodes.`, index);
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
return nodeFromInput(node, id, strict, warnings, index, kind);
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
if (error instanceof DiagramOpError && error.code === "DIAGRAM_UNKNOWN_COMPONENT_TYPE") {
|
|
476
|
+
const entry = error.data?.unknownTypes?.[0];
|
|
477
|
+
if (entry)
|
|
478
|
+
unknown.push(entry);
|
|
479
|
+
return nodeFromInput({ ...node, type: kind === "bpmn" ? "bpmn.task" : "rect", label: node.label ?? node.type }, id, false, [], index, kind);
|
|
480
|
+
}
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
if (unknown.length > 0) {
|
|
485
|
+
throw new DiagramOpError("DIAGRAM_UNKNOWN_COMPONENT_TYPE", `Unknown component type(s): ${unknown.map((entry) => entry.type).join(", ")}. Read callman://diagram/components for the catalog, or pass strictTypes:false to draw them as rectangles.`, -1, { unknownTypes: unknown });
|
|
486
|
+
}
|
|
487
|
+
const typeOf = (id) => nodes.find((node) => node.id === id)?.type;
|
|
488
|
+
for (const node of nodes) {
|
|
489
|
+
const warning = node.parentId ? parentWarning(node.id, node.parentId, typeOf(node.parentId), kind) : null;
|
|
490
|
+
if (warning)
|
|
491
|
+
warnings.push(warning);
|
|
492
|
+
}
|
|
493
|
+
const edgeIds = new Set();
|
|
494
|
+
const edges = (input.edges ?? []).map((edge, index) => {
|
|
495
|
+
const id = edge.id?.trim() || genEdgeId();
|
|
496
|
+
if (edgeIds.has(id) || idSet.has(id))
|
|
497
|
+
throw new DiagramOpError("DIAGRAM_DUPLICATE_EDGE_ID", `Duplicate edge id: ${id}`, index);
|
|
498
|
+
edgeIds.add(id);
|
|
499
|
+
for (const endpoint of [edge.source, edge.target]) {
|
|
500
|
+
if (!idSet.has(endpoint)) {
|
|
501
|
+
throw new DiagramOpError("DIAGRAM_EDGE_ENDPOINT_NOT_FOUND", `Edge ${id} references "${endpoint}" which is not a node id.`, index);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return edgeFromInput(edge, id, typeOf, kind);
|
|
505
|
+
});
|
|
506
|
+
const timestamp = options.createdAt ?? nowIso();
|
|
507
|
+
const document = normalizeDiagramStyles({
|
|
508
|
+
schemaVersion: SCHEMA_VERSION,
|
|
509
|
+
id: options.existingId ?? `doc_${Math.random().toString(36).slice(2, 10)}`,
|
|
510
|
+
meta: { title: input.title.trim(), createdAt: timestamp, updatedAt: options.now ?? nowIso() },
|
|
511
|
+
canvas: {
|
|
512
|
+
background: input.canvas?.background ?? "dots",
|
|
513
|
+
gridSize: input.canvas?.gridSize ?? 16,
|
|
514
|
+
...(input.canvas?.defaultRouting ? { defaultRouting: input.canvas.defaultRouting } : {}),
|
|
515
|
+
},
|
|
516
|
+
nodes,
|
|
517
|
+
edges,
|
|
518
|
+
});
|
|
519
|
+
const parsed = safeParseDiagramDocument(document);
|
|
520
|
+
if (!parsed.ok) {
|
|
521
|
+
throw new DiagramOpError("DIAGRAM_DOCUMENT_INVALID", `The diagram document is invalid: ${parsed.errors.join("; ")}`, -1, { errors: parsed.errors });
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
document: parsed.document,
|
|
525
|
+
warnings,
|
|
526
|
+
unpositionedNodeIds: input.nodes.filter((node) => !node.position).map((_node, index) => ids[index]),
|
|
527
|
+
};
|
|
528
|
+
};
|
|
529
|
+
// A container around the members' bounding box, members re-parented into it
|
|
530
|
+
// with their positions converted so nothing moves on the canvas.
|
|
531
|
+
export const groupNodesOps = (doc, memberIds, options) => {
|
|
532
|
+
const byId = indexNodes(doc.nodes);
|
|
533
|
+
const members = memberIds.map((id) => byId.get(id)).filter((node) => Boolean(node));
|
|
534
|
+
const padding = options.padding ?? 24;
|
|
535
|
+
const titleHeight = options.titleHeight ?? 32;
|
|
536
|
+
const rects = members.map((node) => absoluteRect(node, byId));
|
|
537
|
+
const box = boundingBox(rects) ?? { x: 0, y: 0, width: 200, height: 120 };
|
|
538
|
+
// The container joins the first member's parent so nested groups stay nested.
|
|
539
|
+
const parentId = members[0]?.parentId;
|
|
540
|
+
const type = options.type ?? "frame";
|
|
541
|
+
const def = getDiagramComponent(type);
|
|
542
|
+
const containerAbs = { x: box.x - padding, y: box.y - padding - titleHeight };
|
|
543
|
+
const container = {
|
|
544
|
+
id: options.id,
|
|
545
|
+
type,
|
|
546
|
+
position: toParentRelative(containerAbs, parentId, byId),
|
|
547
|
+
size: { width: box.width + padding * 2, height: box.height + padding * 2 + titleHeight },
|
|
548
|
+
label: options.label ?? def?.defaultLabel ?? "Group",
|
|
549
|
+
...(parentId ? { parentId } : {}),
|
|
550
|
+
};
|
|
551
|
+
const ops = [{ op: "addNode", node: container }];
|
|
552
|
+
const memberPositions = {};
|
|
553
|
+
members.forEach((member, index) => {
|
|
554
|
+
const abs = rects[index];
|
|
555
|
+
const position = { x: abs.x - containerAbs.x, y: abs.y - containerAbs.y };
|
|
556
|
+
memberPositions[member.id] = position;
|
|
557
|
+
ops.push({ op: "reparent", id: member.id, parentId: options.id, position });
|
|
558
|
+
});
|
|
559
|
+
return { ops, container, memberPositions };
|
|
560
|
+
};
|
|
561
|
+
export const ungroupOps = (containerId) => [{ op: "removeNode", id: containerId, children: "lift" }];
|
|
562
|
+
// Place ONLY the given nodes (new ones from a patch) below the current
|
|
563
|
+
// bounding box, in rows — nothing that already has a position moves. Nodes
|
|
564
|
+
// inside a container are placed inside it, below its current content.
|
|
565
|
+
export const placeNewNodesOps = (doc, nodeIds, gap = 40) => {
|
|
566
|
+
const wanted = new Set(nodeIds);
|
|
567
|
+
if (wanted.size === 0)
|
|
568
|
+
return [];
|
|
569
|
+
const byParent = new Map();
|
|
570
|
+
for (const node of doc.nodes) {
|
|
571
|
+
if (!wanted.has(node.id))
|
|
572
|
+
continue;
|
|
573
|
+
const list = byParent.get(node.parentId) ?? [];
|
|
574
|
+
list.push(node);
|
|
575
|
+
byParent.set(node.parentId, list);
|
|
576
|
+
}
|
|
577
|
+
const moves = [];
|
|
578
|
+
for (const [parentId, newcomers] of byParent) {
|
|
579
|
+
const placed = doc.nodes.filter((node) => !wanted.has(node.id) && (node.parentId ?? undefined) === parentId);
|
|
580
|
+
const maxY = placed.reduce((max, node) => Math.max(max, node.position.y + node.size.height), parentId ? 48 : 0);
|
|
581
|
+
const minX = placed.reduce((min, node) => Math.min(min, node.position.x), placed.length > 0 ? Infinity : parentId ? 24 : 0);
|
|
582
|
+
const startX = Number.isFinite(minX) ? minX : 0;
|
|
583
|
+
let cursorX = startX;
|
|
584
|
+
let cursorY = placed.length > 0 ? maxY + gap * 2 : maxY;
|
|
585
|
+
let rowHeight = 0;
|
|
586
|
+
const rowLimit = 1200;
|
|
587
|
+
for (const node of newcomers) {
|
|
588
|
+
if (cursorX + node.size.width > rowLimit && cursorX > startX) {
|
|
589
|
+
cursorX = startX;
|
|
590
|
+
cursorY += rowHeight + gap;
|
|
591
|
+
rowHeight = 0;
|
|
592
|
+
}
|
|
593
|
+
moves.push({ id: node.id, position: { x: cursorX, y: cursorY } });
|
|
594
|
+
cursorX += node.size.width + gap;
|
|
595
|
+
rowHeight = Math.max(rowHeight, node.size.height);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return moves.length > 0 ? [{ op: "moveNodes", moves }] : [];
|
|
599
|
+
};
|
|
600
|
+
// Whether an ops batch (typically from a model) deletes a large part of the
|
|
601
|
+
// diagram — hosts confirm with the user before applying such a batch.
|
|
602
|
+
export const assessOpsRisk = (doc, ops, threshold = 0.3, minimum = 3) => {
|
|
603
|
+
const removes = new Set();
|
|
604
|
+
const collect = (list) => {
|
|
605
|
+
for (const op of list) {
|
|
606
|
+
if (op.op === "removeNode")
|
|
607
|
+
removes.add(op.id);
|
|
608
|
+
else if (op.op === "batch")
|
|
609
|
+
collect(op.ops);
|
|
610
|
+
else if (op.op === "replaceDocument")
|
|
611
|
+
for (const node of doc.nodes)
|
|
612
|
+
if (!op.document.nodes.some((n) => n.id === node.id))
|
|
613
|
+
removes.add(node.id);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
collect(ops);
|
|
617
|
+
const totalNodes = doc.nodes.length;
|
|
618
|
+
const ratio = totalNodes === 0 ? 0 : removes.size / totalNodes;
|
|
619
|
+
return { removesNodes: removes.size, totalNodes, ratio, destructive: removes.size >= minimum && ratio > threshold };
|
|
620
|
+
};
|
|
621
|
+
//# sourceMappingURL=patchInput.js.map
|