callman-mcp 0.2.0 → 0.3.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/CHANGELOG.md +28 -0
- package/README.md +2 -2
- package/dist/client/api.d.ts +1 -0
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +2 -0
- package/dist/client/api.js.map +1 -1
- package/dist/domain/diagram/build.d.ts +112 -63
- package/dist/domain/diagram/build.d.ts.map +1 -1
- package/dist/domain/diagram/build.js +49 -165
- package/dist/domain/diagram/build.js.map +1 -1
- package/dist/domain/diagram/layout.d.ts +3 -4
- package/dist/domain/diagram/layout.d.ts.map +1 -1
- package/dist/domain/diagram/layout.js +12 -45
- package/dist/domain/diagram/layout.js.map +1 -1
- package/dist/domain/diagram/patch.d.ts +298 -80
- package/dist/domain/diagram/patch.d.ts.map +1 -1
- package/dist/domain/diagram/patch.js +60 -273
- package/dist/domain/diagram/patch.js.map +1 -1
- package/dist/domain/diagram/style.d.ts +4 -12
- package/dist/domain/diagram/style.d.ts.map +1 -1
- package/dist/domain/diagram/style.js +8 -84
- package/dist/domain/diagram/style.js.map +1 -1
- package/dist/domain/diagram/summarize.d.ts +25 -24
- package/dist/domain/diagram/summarize.d.ts.map +1 -1
- package/dist/domain/diagram/summarize.js +20 -35
- package/dist/domain/diagram/summarize.js.map +1 -1
- package/dist/domain/requestIntegrations.d.ts +20 -20
- package/dist/domain/requestShape.d.ts +32 -32
- package/dist/resources/docs/diagramSchema.d.ts +1 -1
- package/dist/resources/docs/diagramSchema.d.ts.map +1 -1
- package/dist/resources/docs/diagramSchema.js +55 -10
- package/dist/resources/docs/diagramSchema.js.map +1 -1
- package/dist/resources/generated/aiPacks.d.ts +1 -1
- package/dist/resources/generated/aiPacks.js +2 -2
- package/dist/tools/diagrams.d.ts.map +1 -1
- package/dist/tools/diagrams.js +244 -42
- package/dist/tools/diagrams.js.map +1 -1
- package/dist-cjs/client/api.js +2 -0
- package/dist-cjs/domain/diagram/build.js +53 -169
- package/dist-cjs/domain/diagram/layout.js +11 -44
- package/dist-cjs/domain/diagram/patch.js +57 -270
- package/dist-cjs/domain/diagram/style.js +11 -88
- package/dist-cjs/domain/diagram/summarize.js +22 -35
- package/dist-cjs/resources/docs/diagramSchema.js +55 -10
- package/dist-cjs/resources/generated/aiPacks.js +2 -2
- package/dist-cjs/tools/diagrams.js +240 -38
- package/package.json +2 -2
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildDiagramDocument = exports.
|
|
3
|
+
exports.buildDiagramDocument = exports.assertNotConnector = exports.suggestComponentTypes = exports.parentWarning = exports.isContainerType = exports.edgeStyleFor = exports.containerTypes = exports.toToolError = exports.diagramInputSchema = exports.diagramCanvasInputSchema = exports.diagramEdgeInputSchema = exports.diagramNodeInputSchema = exports.SIDES = exports.ROUTINGS = exports.EDGE_SEMANTICS_INPUT = void 0;
|
|
4
4
|
const callman_core_1 = require("callman-core");
|
|
5
|
+
Object.defineProperty(exports, "containerTypes", { enumerable: true, get: function () { return callman_core_1.containerTypes; } });
|
|
6
|
+
Object.defineProperty(exports, "edgeStyleFor", { enumerable: true, get: function () { return callman_core_1.edgeStyleFor; } });
|
|
7
|
+
Object.defineProperty(exports, "isContainerType", { enumerable: true, get: function () { return callman_core_1.isContainerType; } });
|
|
8
|
+
Object.defineProperty(exports, "parentWarning", { enumerable: true, get: function () { return callman_core_1.parentWarning; } });
|
|
9
|
+
Object.defineProperty(exports, "suggestComponentTypes", { enumerable: true, get: function () { return callman_core_1.suggestComponentTypes; } });
|
|
5
10
|
const zod_1 = require("zod");
|
|
6
11
|
const errors_js_1 = require("../../client/errors.js");
|
|
7
|
-
const ids_js_1 = require("../../lib/ids.js");
|
|
8
12
|
const strict_js_1 = require("../../lib/strict.js");
|
|
9
13
|
const style_js_1 = require("./style.js");
|
|
10
14
|
// From the COMPACT diagram a model writes (typed nodes with labels, edges
|
|
11
|
-
// with a meaning) to the DiagramDocument the Diagram Studio renders.
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
15
|
+
// with a meaning) to the DiagramDocument the Diagram Studio renders. The
|
|
16
|
+
// zod schemas here are the tool contract (strict keys, descriptions); the
|
|
17
|
+
// building itself is callman-core's — the same code the desktop's AI edits
|
|
18
|
+
// and the studio use — so ids, sizes, notation and validation match
|
|
19
|
+
// everywhere. Ids are slugs of the label (`orders-service`, `-2` on a
|
|
20
|
+
// clash) so a model regenerating a diagram keeps them stable.
|
|
16
21
|
exports.EDGE_SEMANTICS_INPUT = ["sync", "async", "fallback", "bus"];
|
|
17
22
|
const ARROWS = ["none", "classic", "classicThin", "open", "block", "diamond", "oval"];
|
|
23
|
+
exports.ROUTINGS = ["orthogonal", "straight", "curved", "smart"];
|
|
24
|
+
exports.SIDES = ["top", "right", "bottom", "left"];
|
|
18
25
|
exports.diagramNodeInputSchema = (0, strict_js_1.strictObject)({
|
|
19
|
-
id: zod_1.z.string().min(1).max(64).optional().describe("Stable id (
|
|
20
|
-
type: zod_1.z.string().min(1).max(80).describe("Component type from callman://diagram/components, e.g. net.service, net.api-gateway, db.postgres, mq.kafka, aws.lambda, flow.process"),
|
|
26
|
+
id: zod_1.z.string().min(1).max(64).optional().describe("Stable id (orders, api, db…); a slug of the label when omitted"),
|
|
27
|
+
type: zod_1.z.string().min(1).max(80).describe("Component type from callman://diagram/components, e.g. net.service, net.api-gateway, db.postgres, mq.kafka, aws.lambda, flow.process. On a BPMN diagram: a bpmn.* type (bpmn.user-task, bpmn.exclusive-gateway, bpmn.start-event, bpmn.pool, bpmn.lane …)"),
|
|
21
28
|
label: zod_1.z.string().max(200).optional(),
|
|
22
29
|
parent: zod_1.z.string().max(64).optional().describe("Id of a container node this node sits inside: frame, pool.horizontal or pool.vertical"),
|
|
23
30
|
position: zod_1.z.object({ x: zod_1.z.number(), y: zod_1.z.number() }).strict().optional().describe("Top-left corner; relative to the parent container when `parent` is set. Omit to auto-layout"),
|
|
@@ -26,6 +33,7 @@ exports.diagramNodeInputSchema = (0, strict_js_1.strictObject)({
|
|
|
26
33
|
rotation: zod_1.z.number().min(-360).max(360).optional().describe("Degrees, clockwise"),
|
|
27
34
|
locked: zod_1.z.boolean().optional().describe("Locked nodes cannot be moved or resized in the studio"),
|
|
28
35
|
hidden: zod_1.z.boolean().optional(),
|
|
36
|
+
collapsed: zod_1.z.boolean().optional().describe("Containers only: drawn folded with the children hidden"),
|
|
29
37
|
data: zod_1.z.record(zod_1.z.unknown()).optional().describe("Free-form metadata kept with the node (not rendered), e.g. { owner: \"team-a\", repo: \"…\" }"),
|
|
30
38
|
}, "node field");
|
|
31
39
|
exports.diagramEdgeInputSchema = (0, strict_js_1.strictObject)({
|
|
@@ -35,181 +43,57 @@ exports.diagramEdgeInputSchema = (0, strict_js_1.strictObject)({
|
|
|
35
43
|
label: zod_1.z.string().max(200).optional(),
|
|
36
44
|
semantic: zod_1.z.enum(exports.EDGE_SEMANTICS_INPUT).optional().describe("sync (default, solid) = request/response, async (dashed) = event, fallback (dotted) = retry/optional, bus (heavy) = message bus; automatic bus when an end is a messaging component"),
|
|
37
45
|
color: style_js_1.colorInputSchema.optional().describe('Line CSS color, e.g. "#8B5CF6". Independent of `semantic`'),
|
|
38
|
-
routing: zod_1.z.enum(
|
|
46
|
+
routing: zod_1.z.enum(exports.ROUTINGS).optional().describe("orthogonal (default, right-angle auto-routing), straight, curved, smart (routed around other shapes)"),
|
|
39
47
|
markerEnd: zod_1.z.enum(ARROWS).optional().describe("Arrow at the target (default classicThin)"),
|
|
40
48
|
markerStart: zod_1.z.enum(ARROWS).optional().describe("Arrow at the source (default none) — set it for a two-way connection"),
|
|
49
|
+
sourceSide: zod_1.z.enum(exports.SIDES).optional().describe("Fix which side of the source node the line leaves from (default: floating — chosen from the relative position)"),
|
|
50
|
+
targetSide: zod_1.z.enum(exports.SIDES).optional().describe("Fix which side of the target node the line enters"),
|
|
51
|
+
data: zod_1.z.record(zod_1.z.unknown()).optional().describe('Free-form metadata kept with the edge. On a BPMN diagram: { bpmn: { flow?: "sequence"|"message", conditionExpression?: string, isDefault?: boolean } }'),
|
|
41
52
|
}, "edge field");
|
|
53
|
+
exports.diagramCanvasInputSchema = zod_1.z
|
|
54
|
+
.object({
|
|
55
|
+
background: zod_1.z.enum(["grid", "dots", "blank"]).optional(),
|
|
56
|
+
gridSize: zod_1.z.number().int().positive().optional(),
|
|
57
|
+
defaultRouting: zod_1.z.enum(exports.ROUTINGS).optional().describe("Routing for edges that do not set their own (default orthogonal)"),
|
|
58
|
+
})
|
|
59
|
+
.strict()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Canvas background (default dots), grid size (default 16) and the default edge routing");
|
|
42
62
|
exports.diagramInputSchema = zod_1.z.object({
|
|
43
63
|
title: zod_1.z.string().min(1).max(200),
|
|
44
64
|
nodes: zod_1.z.array(exports.diagramNodeInputSchema).min(1).max(500),
|
|
45
65
|
edges: zod_1.z.array(exports.diagramEdgeInputSchema).max(2000).default([]),
|
|
46
|
-
canvas:
|
|
47
|
-
.object({ background: zod_1.z.enum(["grid", "dots", "blank"]).optional(), gridSize: zod_1.z.number().int().positive().optional() })
|
|
48
|
-
.strict()
|
|
49
|
-
.optional()
|
|
50
|
-
.describe("Canvas background (default dots) and grid size (default 16)"),
|
|
66
|
+
canvas: exports.diagramCanvasInputSchema,
|
|
51
67
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
seen.add(candidate);
|
|
58
|
-
out.push(candidate);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
for (const entry of (0, callman_core_1.searchDiagramComponents)(type))
|
|
62
|
-
push(entry.type);
|
|
63
|
-
// A typo usually sits at the END of the last segment ("db.postgresqlx"):
|
|
64
|
-
// shrink it until something in the catalog contains it.
|
|
65
|
-
const tail = (type.split(".").pop() ?? type).toLowerCase();
|
|
66
|
-
for (let length = tail.length; length >= 3 && out.length < limit; length -= 1) {
|
|
67
|
-
const needle = tail.slice(0, length);
|
|
68
|
-
for (const entry of callman_core_1.DIAGRAM_COMPONENT_CATALOG) {
|
|
69
|
-
const haystack = [entry.type, entry.label, ...(entry.aliases ?? [])].join(" ").toLowerCase();
|
|
70
|
-
if (haystack.includes(needle))
|
|
71
|
-
push(entry.type);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// Still nothing: offer the same namespace ("db.*", "aws.*").
|
|
75
|
-
const namespace = type.includes(".") ? `${type.split(".")[0].toLowerCase()}.` : null;
|
|
76
|
-
if (namespace) {
|
|
77
|
-
for (const entry of callman_core_1.DIAGRAM_COMPONENT_CATALOG)
|
|
78
|
-
if (entry.type.toLowerCase().startsWith(namespace))
|
|
79
|
-
push(entry.type);
|
|
80
|
-
}
|
|
81
|
-
return out;
|
|
68
|
+
// Core's op errors carry MCP's own codes; only the class changes.
|
|
69
|
+
const toToolError = (error) => {
|
|
70
|
+
if (error instanceof callman_core_1.DiagramOpError)
|
|
71
|
+
throw new errors_js_1.ToolInputError(error.code, error.message, error.data);
|
|
72
|
+
throw error;
|
|
82
73
|
};
|
|
83
|
-
exports.
|
|
84
|
-
// Containers hold children through `parent`: frames and swimlane pools.
|
|
85
|
-
const isContainerType = (type) => {
|
|
86
|
-
const def = type ? (0, callman_core_1.getDiagramComponent)(type) : undefined;
|
|
87
|
-
return Boolean(def?.container || def?.swimlane);
|
|
88
|
-
};
|
|
89
|
-
exports.isContainerType = isContainerType;
|
|
90
|
-
const containerTypes = () => callman_core_1.DIAGRAM_COMPONENT_CATALOG.filter((entry) => entry.container || entry.swimlane).map((entry) => entry.type);
|
|
91
|
-
exports.containerTypes = containerTypes;
|
|
92
|
-
const parentWarning = (id, parentId, parentType) => (0, exports.isContainerType)(parentType)
|
|
93
|
-
? null
|
|
94
|
-
: `Node ${id} sits inside "${parentId}" (${parentType ?? "unknown"}), which is not a container — use ${(0, exports.containerTypes)().join(", ")} to group nodes.`;
|
|
95
|
-
exports.parentWarning = parentWarning;
|
|
96
|
-
// Edge input → the stored edge's visual fields: the semantic's line tuple
|
|
97
|
-
// plus an optional color.
|
|
98
|
-
// line / arrow / line.dashed are palette connectors (0-height), not nodes.
|
|
74
|
+
exports.toToolError = toToolError;
|
|
99
75
|
const assertNotConnector = (type, id) => {
|
|
100
|
-
|
|
101
|
-
|
|
76
|
+
try {
|
|
77
|
+
(0, callman_core_1.assertNotConnector)(type, id);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
(0, exports.toToolError)(error);
|
|
102
81
|
}
|
|
103
82
|
};
|
|
104
83
|
exports.assertNotConnector = assertNotConnector;
|
|
105
|
-
const edgeStyleFor = (semantic, color) => {
|
|
106
|
-
const base = semantic ? (0, callman_core_1.semanticStyle)(semantic) : undefined;
|
|
107
|
-
if (!color)
|
|
108
|
-
return base;
|
|
109
|
-
return { ...(base ?? {}), stroke: (0, style_js_1.toTokenRef)(color) };
|
|
110
|
-
};
|
|
111
|
-
exports.edgeStyleFor = edgeStyleFor;
|
|
112
84
|
const buildDiagramDocument = (input, options = {}) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
const idSet = new Set(ids);
|
|
124
|
-
const unknown = [];
|
|
125
|
-
const typeOf = new Map();
|
|
126
|
-
const nodes = input.nodes.map((node, index) => {
|
|
127
|
-
const id = ids[index];
|
|
128
|
-
let type = node.type.trim();
|
|
129
|
-
const known = (0, callman_core_1.getDiagramComponent)(type);
|
|
130
|
-
if (!known) {
|
|
131
|
-
const suggestions = (0, exports.suggestComponentTypes)(type);
|
|
132
|
-
if (strict)
|
|
133
|
-
unknown.push({ type, suggestions });
|
|
134
|
-
else {
|
|
135
|
-
warnings.push(`Unknown component type "${type}" on ${id} → drawn as a rectangle (suggestions: ${suggestions.join(", ") || "none"})`);
|
|
136
|
-
type = "rect";
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (known)
|
|
140
|
-
(0, exports.assertNotConnector)(type, id);
|
|
141
|
-
const def = (0, callman_core_1.getDiagramComponent)(type);
|
|
142
|
-
typeOf.set(id, type);
|
|
143
|
-
if (node.parent && !idSet.has(node.parent)) {
|
|
144
|
-
throw new errors_js_1.ToolInputError("DIAGRAM_PARENT_NOT_FOUND", `Node ${id} references parent "${node.parent}" which is not in nodes.`);
|
|
145
|
-
}
|
|
146
|
-
return {
|
|
147
|
-
id,
|
|
148
|
-
type,
|
|
149
|
-
position: node.position ?? { x: 0, y: 0 },
|
|
150
|
-
size: node.size ?? def?.defaultSize ?? { width: 140, height: 60 },
|
|
151
|
-
...(node.label !== undefined || !known ? { label: node.label ?? node.type } : def?.defaultLabel ? { label: def.defaultLabel } : {}),
|
|
152
|
-
...(node.parent ? { parentId: node.parent } : {}),
|
|
153
|
-
...(node.style ? { style: (0, style_js_1.applyStyleInput)(undefined, node.style) } : {}),
|
|
154
|
-
...(node.rotation ? { rotation: node.rotation } : {}),
|
|
155
|
-
...(node.locked ? { locked: true } : {}),
|
|
156
|
-
...(node.hidden ? { hidden: true } : {}),
|
|
157
|
-
...(node.data ? { data: node.data } : {}),
|
|
158
|
-
};
|
|
159
|
-
});
|
|
160
|
-
for (const node of nodes) {
|
|
161
|
-
const warning = node.parentId ? (0, exports.parentWarning)(node.id, node.parentId, typeOf.get(node.parentId)) : null;
|
|
162
|
-
if (warning)
|
|
163
|
-
warnings.push(warning);
|
|
164
|
-
}
|
|
165
|
-
if (unknown.length > 0) {
|
|
166
|
-
throw new errors_js_1.ToolInputError("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.`, { unknownTypes: unknown });
|
|
85
|
+
try {
|
|
86
|
+
const built = (0, callman_core_1.buildDiagramFromInput)(input, {
|
|
87
|
+
strictTypes: options.strictTypes !== false,
|
|
88
|
+
existingId: options.existingId,
|
|
89
|
+
createdAt: options.createdAt,
|
|
90
|
+
idStrategy: "slug",
|
|
91
|
+
...(options.kind ? { kind: options.kind } : {}),
|
|
92
|
+
});
|
|
93
|
+
return { document: built.document, warnings: built.warnings, unpositionedNodeIds: built.unpositionedNodeIds };
|
|
167
94
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const id = edge.id ?? nextEdgeId();
|
|
171
|
-
if (edgeIds.has(id))
|
|
172
|
-
throw new errors_js_1.ToolInputError("DIAGRAM_DUPLICATE_EDGE_ID", `Duplicate edge id: ${id}`);
|
|
173
|
-
edgeIds.add(id);
|
|
174
|
-
for (const endpoint of [edge.source, edge.target]) {
|
|
175
|
-
if (!idSet.has(endpoint)) {
|
|
176
|
-
throw new errors_js_1.ToolInputError("DIAGRAM_EDGE_ENDPOINT_NOT_FOUND", `Edge ${id} references "${endpoint}" which is not a node id.`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
let semantic = edge.semantic;
|
|
180
|
-
// A fresh edge into a broker reads as a bus line — the studio's own rule.
|
|
181
|
-
if (!semantic && ((0, callman_core_1.isBusComponentType)(typeOf.get(edge.source)) || (0, callman_core_1.isBusComponentType)(typeOf.get(edge.target)))) {
|
|
182
|
-
semantic = "bus";
|
|
183
|
-
}
|
|
184
|
-
const style = (0, exports.edgeStyleFor)(semantic, edge.color);
|
|
185
|
-
return {
|
|
186
|
-
id,
|
|
187
|
-
source: edge.source,
|
|
188
|
-
target: edge.target,
|
|
189
|
-
...(edge.label ? { label: edge.label } : {}),
|
|
190
|
-
routing: edge.routing ?? "orthogonal",
|
|
191
|
-
markerEnd: edge.markerEnd ?? "classicThin",
|
|
192
|
-
...(edge.markerStart && edge.markerStart !== "none" ? { markerStart: edge.markerStart } : {}),
|
|
193
|
-
...(style && (!(0, callman_core_1.isDefaultEdgeStyle)(style) || semantic === "sync" || edge.color) ? { style } : {}),
|
|
194
|
-
};
|
|
195
|
-
});
|
|
196
|
-
const timestamp = options.createdAt ?? (0, callman_core_1.nowIso)();
|
|
197
|
-
const document = (0, callman_core_1.normalizeDiagramStyles)({
|
|
198
|
-
schemaVersion: callman_core_1.SCHEMA_VERSION,
|
|
199
|
-
id: options.existingId ?? `doc_${Math.random().toString(36).slice(2, 10)}`,
|
|
200
|
-
meta: { title: input.title.trim(), createdAt: timestamp, updatedAt: (0, callman_core_1.nowIso)() },
|
|
201
|
-
canvas: { background: input.canvas?.background ?? "dots", gridSize: input.canvas?.gridSize ?? 16 },
|
|
202
|
-
nodes,
|
|
203
|
-
edges,
|
|
204
|
-
});
|
|
205
|
-
const parsed = (0, callman_core_1.safeParseDiagramDocument)(document);
|
|
206
|
-
if (!parsed.ok) {
|
|
207
|
-
throw new errors_js_1.ToolInputError("DIAGRAM_DOCUMENT_INVALID", `The diagram document is invalid: ${parsed.errors.join("; ")}`, { errors: parsed.errors });
|
|
95
|
+
catch (error) {
|
|
96
|
+
return (0, exports.toToolError)(error);
|
|
208
97
|
}
|
|
209
|
-
return {
|
|
210
|
-
document: parsed.document,
|
|
211
|
-
warnings,
|
|
212
|
-
unpositionedNodeIds: input.nodes.filter((node) => !node.position).map((_node, index) => ids[index]),
|
|
213
|
-
};
|
|
214
98
|
};
|
|
215
99
|
exports.buildDiagramDocument = buildDiagramDocument;
|
|
@@ -45,54 +45,21 @@ const loadElk = () => {
|
|
|
45
45
|
return defaultEngine;
|
|
46
46
|
};
|
|
47
47
|
const layoutDocument = async (doc, options = {}) => {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
return {
|
|
59
|
-
document: touch((0, callman_core_1.gridLayout)(doc)),
|
|
60
|
-
engine: "grid",
|
|
61
|
-
warning: `Automatic layout fell back to a grid: ${error instanceof Error ? error.message : String(error)}`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
48
|
+
const engine = options.engine ?? (await loadElk());
|
|
49
|
+
const result = await (0, callman_core_1.runLayout)(doc, {
|
|
50
|
+
algorithm: options.algorithm ?? "layered",
|
|
51
|
+
direction: options.direction ?? "DOWN",
|
|
52
|
+
mode: options.nodeIds && options.nodeIds.length > 0 ? "selection" : "full",
|
|
53
|
+
nodeIds: options.nodeIds,
|
|
54
|
+
}, engine);
|
|
55
|
+
const document = result.ops.length > 0 ? (0, callman_core_1.applyDiagramOps)(doc, result.ops, { now: new Date().toISOString() }).doc : doc;
|
|
56
|
+
return { document, engine: result.engine, ...(result.warning ? { warning: result.warning } : {}) };
|
|
64
57
|
};
|
|
65
58
|
exports.layoutDocument = layoutDocument;
|
|
66
59
|
// Place ONLY the given nodes (new ones from a patch) below the current
|
|
67
60
|
// bounding box, in rows — nothing that already has a position moves.
|
|
68
61
|
const placeNewNodes = (doc, nodeIds, gap = 40) => {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
return doc;
|
|
72
|
-
const placed = doc.nodes.filter((node) => !wanted.has(node.id) && !node.parentId);
|
|
73
|
-
const maxY = placed.reduce((max, node) => Math.max(max, node.position.y + node.size.height), 0);
|
|
74
|
-
const minX = placed.reduce((min, node) => Math.min(min, node.position.x), placed.length > 0 ? Infinity : 0);
|
|
75
|
-
let cursorX = Number.isFinite(minX) ? minX : 0;
|
|
76
|
-
let cursorY = placed.length > 0 ? maxY + gap * 2 : 0;
|
|
77
|
-
let rowHeight = 0;
|
|
78
|
-
const rowLimit = 1200;
|
|
79
|
-
const nodes = doc.nodes.map((node) => {
|
|
80
|
-
if (!wanted.has(node.id))
|
|
81
|
-
return node;
|
|
82
|
-
if (cursorX + node.size.width > rowLimit && cursorX > 0) {
|
|
83
|
-
cursorX = Number.isFinite(minX) ? minX : 0;
|
|
84
|
-
cursorY += rowHeight + gap;
|
|
85
|
-
rowHeight = 0;
|
|
86
|
-
}
|
|
87
|
-
const positioned = { ...node, position: { x: cursorX, y: cursorY } };
|
|
88
|
-
cursorX += node.size.width + gap;
|
|
89
|
-
rowHeight = Math.max(rowHeight, node.size.height);
|
|
90
|
-
return positioned;
|
|
91
|
-
});
|
|
92
|
-
return touch({ ...doc, nodes });
|
|
62
|
+
const ops = (0, callman_core_1.placeNewNodesOps)(doc, nodeIds, gap);
|
|
63
|
+
return ops.length > 0 ? (0, callman_core_1.applyDiagramOps)(doc, ops, { now: new Date().toISOString() }).doc : doc;
|
|
93
64
|
};
|
|
94
65
|
exports.placeNewNodes = placeNewNodes;
|
|
95
|
-
const touch = (doc) => ({
|
|
96
|
-
...doc,
|
|
97
|
-
meta: { ...doc.meta, updatedAt: new Date().toISOString() },
|
|
98
|
-
});
|