drowai-mcp 0.1.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/dist/cli.js ADDED
@@ -0,0 +1,3126 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { realpathSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ import Fastify from "fastify";
9
+
10
+ var error = (code, message, options = {}) => {
11
+ const result = {
12
+ code,
13
+ message,
14
+ severity: "error"
15
+ };
16
+ if (options.fieldPath !== void 0) {
17
+ result.fieldPath = options.fieldPath;
18
+ }
19
+ if (options.objectId !== void 0) {
20
+ result.objectId = options.objectId;
21
+ }
22
+ return result;
23
+ };
24
+ var warning = (code, message, options = {}) => {
25
+ const result = {
26
+ code,
27
+ message,
28
+ severity: "warning"
29
+ };
30
+ if (options.fieldPath !== void 0) {
31
+ result.fieldPath = options.fieldPath;
32
+ }
33
+ if (options.objectId !== void 0) {
34
+ result.objectId = options.objectId;
35
+ }
36
+ return result;
37
+ };
38
+
39
+ import { ulid } from "ulid";
40
+
41
+ var DOCUMENT_ID_PREFIX = "doc_";
42
+ var NODE_ID_PREFIX = "node_";
43
+ var EDGE_ID_PREFIX = "edge_";
44
+ var NODE_TYPES = [
45
+ "rectangle",
46
+ "roundedRectangle",
47
+ "ellipse",
48
+ "diamond",
49
+ "text"
50
+ ];
51
+ var EDGE_PATH_TYPES = ["straight", "orthogonal"];
52
+ var STYLE_PRESETS = ["default", "emphasis", "warning", "muted"];
53
+ var STROKE_WIDTHS = [1, 2, 3, 4, 6];
54
+ var FONT_SIZES = [12, 14, 16, 20, 24, 32];
55
+ var EDGE_FONT_SIZES = [12, 14, 16, 20, 24];
56
+ var FONT_WEIGHTS = ["normal", "bold"];
57
+ var TEXT_ALIGNS = ["left", "center", "right"];
58
+ var VERTICAL_ALIGNS = ["top", "middle", "bottom"];
59
+ var ARROW_TYPES = ["none", "classic", "block", "open"];
60
+ var ANCHOR_SIDES = ["top", "right", "bottom", "left"];
61
+
62
+ var ID_PATTERNS = {
63
+ [DOCUMENT_ID_PREFIX]: /^doc_[a-z0-9][a-z0-9_-]{2,63}$/,
64
+ [NODE_ID_PREFIX]: /^node_[a-z0-9][a-z0-9_-]{2,63}$/,
65
+ [EDGE_ID_PREFIX]: /^edge_[a-z0-9][a-z0-9_-]{2,63}$/
66
+ };
67
+ var generateDocumentId = () => `${DOCUMENT_ID_PREFIX}${ulid().toLowerCase()}`;
68
+ var generateNodeId = () => `${NODE_ID_PREFIX}${ulid().toLowerCase()}`;
69
+ var generateEdgeId = () => `${EDGE_ID_PREFIX}${ulid().toLowerCase()}`;
70
+
71
+ var BASE_NODE_PRESETS = {
72
+ default: {
73
+ fillColor: "#ffffff",
74
+ strokeColor: "#1f2937",
75
+ strokeWidth: 1,
76
+ dashed: false,
77
+ rounded: false,
78
+ textColor: "#111827",
79
+ fontSize: 14,
80
+ fontWeight: "normal",
81
+ textAlign: "center",
82
+ verticalAlign: "middle",
83
+ wrap: true
84
+ },
85
+ emphasis: {
86
+ fillColor: "#dbeafe",
87
+ strokeColor: "#1d4ed8",
88
+ strokeWidth: 2,
89
+ dashed: false,
90
+ rounded: false,
91
+ textColor: "#1e3a8a",
92
+ fontSize: 14,
93
+ fontWeight: "bold",
94
+ textAlign: "center",
95
+ verticalAlign: "middle",
96
+ wrap: true
97
+ },
98
+ warning: {
99
+ fillColor: "#fef3c7",
100
+ strokeColor: "#d97706",
101
+ strokeWidth: 2,
102
+ dashed: false,
103
+ rounded: false,
104
+ textColor: "#92400e",
105
+ fontSize: 14,
106
+ fontWeight: "bold",
107
+ textAlign: "center",
108
+ verticalAlign: "middle",
109
+ wrap: true
110
+ },
111
+ muted: {
112
+ fillColor: "#f3f4f6",
113
+ strokeColor: "#9ca3af",
114
+ strokeWidth: 1,
115
+ dashed: true,
116
+ rounded: false,
117
+ textColor: "#6b7280",
118
+ fontSize: 14,
119
+ fontWeight: "normal",
120
+ textAlign: "center",
121
+ verticalAlign: "middle",
122
+ wrap: true
123
+ }
124
+ };
125
+ var BASE_EDGE_PRESETS = {
126
+ default: {
127
+ strokeColor: "#1f2937",
128
+ strokeWidth: 1,
129
+ dashed: false,
130
+ textColor: "#111827",
131
+ fontSize: 14,
132
+ fontWeight: "normal",
133
+ textAlign: "center",
134
+ verticalAlign: "middle",
135
+ wrap: true,
136
+ arrowStart: "none",
137
+ arrowEnd: "classic"
138
+ },
139
+ emphasis: {
140
+ strokeColor: "#1d4ed8",
141
+ strokeWidth: 2,
142
+ dashed: false,
143
+ textColor: "#1e3a8a",
144
+ fontSize: 14,
145
+ fontWeight: "bold",
146
+ textAlign: "center",
147
+ verticalAlign: "middle",
148
+ wrap: true,
149
+ arrowStart: "none",
150
+ arrowEnd: "classic"
151
+ },
152
+ warning: {
153
+ strokeColor: "#d97706",
154
+ strokeWidth: 2,
155
+ dashed: false,
156
+ textColor: "#92400e",
157
+ fontSize: 14,
158
+ fontWeight: "bold",
159
+ textAlign: "center",
160
+ verticalAlign: "middle",
161
+ wrap: true,
162
+ arrowStart: "none",
163
+ arrowEnd: "classic"
164
+ },
165
+ muted: {
166
+ strokeColor: "#9ca3af",
167
+ strokeWidth: 1,
168
+ dashed: true,
169
+ textColor: "#6b7280",
170
+ fontSize: 14,
171
+ fontWeight: "normal",
172
+ textAlign: "center",
173
+ verticalAlign: "middle",
174
+ wrap: true,
175
+ arrowStart: "none",
176
+ arrowEnd: "open"
177
+ }
178
+ };
179
+ var NODE_STYLE_KEYS = [
180
+ "preset",
181
+ "fillColor",
182
+ "strokeColor",
183
+ "strokeWidth",
184
+ "dashed",
185
+ "rounded",
186
+ "textColor",
187
+ "fontSize",
188
+ "fontWeight",
189
+ "textAlign",
190
+ "verticalAlign",
191
+ "wrap"
192
+ ];
193
+ var EDGE_STYLE_KEYS = [
194
+ "preset",
195
+ "strokeColor",
196
+ "strokeWidth",
197
+ "dashed",
198
+ "textColor",
199
+ "fontSize",
200
+ "fontWeight",
201
+ "textAlign",
202
+ "verticalAlign",
203
+ "wrap",
204
+ "arrowStart",
205
+ "arrowEnd"
206
+ ];
207
+ var resolveNodeStyle = (style, forceRounded) => {
208
+ const preset = style?.preset ?? "default";
209
+ const resolved = {
210
+ preset,
211
+ ...BASE_NODE_PRESETS[preset],
212
+ ...style
213
+ };
214
+ if (forceRounded) {
215
+ resolved.rounded = true;
216
+ }
217
+ return resolved;
218
+ };
219
+ var resolveEdgeStyle = (style) => {
220
+ const preset = style?.preset ?? "default";
221
+ return {
222
+ preset,
223
+ ...BASE_EDGE_PRESETS[preset],
224
+ ...style
225
+ };
226
+ };
227
+
228
+ var ANCHOR_ORDER = ["right", "bottom", "left", "top"];
229
+ var nodeCenter = (node) => ({
230
+ x: node.x + Math.round(node.width / 2),
231
+ y: node.y + Math.round(node.height / 2)
232
+ });
233
+ var axisOf = (side) => side === "left" || side === "right" ? "horizontal" : "vertical";
234
+ var anchorRank = {
235
+ right: 0,
236
+ bottom: 1,
237
+ left: 2,
238
+ top: 3
239
+ };
240
+ var getAnchorPoint = (node, side) => {
241
+ switch (side) {
242
+ case "top":
243
+ return { x: node.x + Math.round(node.width / 2), y: node.y };
244
+ case "right":
245
+ return { x: node.x + node.width, y: node.y + Math.round(node.height / 2) };
246
+ case "bottom":
247
+ return { x: node.x + Math.round(node.width / 2), y: node.y + node.height };
248
+ case "left":
249
+ return { x: node.x, y: node.y + Math.round(node.height / 2) };
250
+ }
251
+ };
252
+ var fallbackAnchors = (source, target) => {
253
+ const sourceCenter = nodeCenter(source);
254
+ const targetCenter = nodeCenter(target);
255
+ const dx = targetCenter.x - sourceCenter.x;
256
+ const dy = targetCenter.y - sourceCenter.y;
257
+ if (Math.abs(dx) >= Math.abs(dy)) {
258
+ return dx >= 0 ? { sourceAnchor: "right", targetAnchor: "left" } : { sourceAnchor: "left", targetAnchor: "right" };
259
+ }
260
+ return dy >= 0 ? { sourceAnchor: "bottom", targetAnchor: "top" } : { sourceAnchor: "top", targetAnchor: "bottom" };
261
+ };
262
+ var roundPoint = (point) => ({
263
+ x: Math.round(point.x),
264
+ y: Math.round(point.y)
265
+ });
266
+ var dedupeRoutePoints = (points) => {
267
+ const result = [];
268
+ for (const point of points) {
269
+ const previous = result.at(-1);
270
+ const rounded = roundPoint(point);
271
+ if (!previous || previous.x !== rounded.x || previous.y !== rounded.y) {
272
+ result.push(rounded);
273
+ }
274
+ }
275
+ return result;
276
+ };
277
+ var preferredAxis = (source, target) => {
278
+ const horizontalGap = Math.max(target.x - (source.x + source.width), source.x - (target.x + target.width), 0);
279
+ const verticalGap = Math.max(target.y - (source.y + source.height), source.y - (target.y + target.height), 0);
280
+ if (horizontalGap !== verticalGap) {
281
+ return horizontalGap > verticalGap ? "horizontal" : "vertical";
282
+ }
283
+ const sourceCenter = nodeCenter(source);
284
+ const targetCenter = nodeCenter(target);
285
+ const dx = Math.abs(targetCenter.x - sourceCenter.x);
286
+ const dy = Math.abs(targetCenter.y - sourceCenter.y);
287
+ if (dx === dy) {
288
+ return null;
289
+ }
290
+ return dx > dy ? "horizontal" : "vertical";
291
+ };
292
+ var buildRawRoutePoints = (sourcePoint, targetPoint, sourceAnchor, targetAnchor) => {
293
+ const sourceAxis = axisOf(sourceAnchor);
294
+ const targetAxis = axisOf(targetAnchor);
295
+ if (sourceAxis !== targetAxis) {
296
+ return sourceAxis === "horizontal" ? [{ x: targetPoint.x, y: sourcePoint.y }] : [{ x: sourcePoint.x, y: targetPoint.y }];
297
+ }
298
+ if (sourceAxis === "horizontal") {
299
+ const midpointX = Math.round((sourcePoint.x + targetPoint.x) / 2);
300
+ if (sourcePoint.y === targetPoint.y) {
301
+ return [{ x: midpointX, y: sourcePoint.y }];
302
+ }
303
+ return [
304
+ { x: midpointX, y: sourcePoint.y },
305
+ { x: midpointX, y: targetPoint.y }
306
+ ];
307
+ }
308
+ const midpointY = Math.round((sourcePoint.y + targetPoint.y) / 2);
309
+ if (sourcePoint.x === targetPoint.x) {
310
+ return [{ x: sourcePoint.x, y: midpointY }];
311
+ }
312
+ return [
313
+ { x: sourcePoint.x, y: midpointY },
314
+ { x: targetPoint.x, y: midpointY }
315
+ ];
316
+ };
317
+ var normalizeCandidatePoints = (sourcePoint, targetPoint, points) => {
318
+ const deduped = dedupeRoutePoints(points);
319
+ if (deduped.length <= 1) {
320
+ return deduped;
321
+ }
322
+ const fullPath = [sourcePoint, ...deduped, targetPoint];
323
+ const simplified = [];
324
+ for (let index = 1; index < fullPath.length - 1; index += 1) {
325
+ const previous = fullPath[index - 1];
326
+ const current = fullPath[index];
327
+ const next = fullPath[index + 1];
328
+ const isCollinear = previous.x === current.x && current.x === next.x || previous.y === current.y && current.y === next.y;
329
+ if (!isCollinear) {
330
+ simplified.push(current);
331
+ }
332
+ }
333
+ return simplified;
334
+ };
335
+ var directionOf = (from, to) => {
336
+ if (from.x === to.x && from.y === to.y) {
337
+ return null;
338
+ }
339
+ if (from.x !== to.x && from.y !== to.y) {
340
+ return null;
341
+ }
342
+ if (to.x > from.x) {
343
+ return "right";
344
+ }
345
+ if (to.x < from.x) {
346
+ return "left";
347
+ }
348
+ return to.y > from.y ? "bottom" : "top";
349
+ };
350
+ var countBends = (path) => {
351
+ let bends = 0;
352
+ let previousDirection = null;
353
+ for (let index = 1; index < path.length; index += 1) {
354
+ const direction = directionOf(path[index - 1], path[index]);
355
+ if (!direction) {
356
+ return Number.POSITIVE_INFINITY;
357
+ }
358
+ if (previousDirection && previousDirection !== direction) {
359
+ bends += 1;
360
+ }
361
+ previousDirection = direction;
362
+ }
363
+ return bends;
364
+ };
365
+ var totalLength = (path) => path.slice(1).reduce((sum, point, index) => {
366
+ const previous = path[index];
367
+ return sum + Math.abs(point.x - previous.x) + Math.abs(point.y - previous.y);
368
+ }, 0);
369
+ var facingPenalty = (anchor, from, to) => {
370
+ switch (anchor) {
371
+ case "right":
372
+ return to.x >= from.x ? 0 : 2;
373
+ case "left":
374
+ return to.x <= from.x ? 0 : 2;
375
+ case "bottom":
376
+ return to.y >= from.y ? 0 : 2;
377
+ case "top":
378
+ return to.y <= from.y ? 0 : 2;
379
+ }
380
+ };
381
+ var stickinessPenalty = (sourceAnchor, targetAnchor, preferred) => {
382
+ if (!preferred?.sourceAnchor && !preferred?.targetAnchor) {
383
+ return 0;
384
+ }
385
+ let penalty = 0;
386
+ if (preferred.sourceAnchor && preferred.sourceAnchor !== sourceAnchor) {
387
+ penalty += 1;
388
+ }
389
+ if (preferred.targetAnchor && preferred.targetAnchor !== targetAnchor) {
390
+ penalty += 1;
391
+ }
392
+ return penalty;
393
+ };
394
+ var buildCandidate = (source, target, sourceAnchor, targetAnchor, preferred, preferredRouteAxis) => {
395
+ const sourceCenter = nodeCenter(source);
396
+ const targetCenter = nodeCenter(target);
397
+ const sourcePoint = getAnchorPoint(source, sourceAnchor);
398
+ const targetPoint = getAnchorPoint(target, targetAnchor);
399
+ const points = normalizeCandidatePoints(
400
+ sourcePoint,
401
+ targetPoint,
402
+ buildRawRoutePoints(sourcePoint, targetPoint, sourceAnchor, targetAnchor)
403
+ );
404
+ const path = [sourcePoint, ...points, targetPoint];
405
+ const startDirection = directionOf(sourcePoint, path[1]);
406
+ const targetEntryDirection = directionOf(targetPoint, path[path.length - 2]);
407
+ if (!startDirection || !targetEntryDirection) {
408
+ return null;
409
+ }
410
+ if (startDirection !== sourceAnchor || targetEntryDirection !== targetAnchor) {
411
+ return null;
412
+ }
413
+ const bends = countBends(path);
414
+ if (!Number.isFinite(bends)) {
415
+ return null;
416
+ }
417
+ return {
418
+ sourceAnchor,
419
+ targetAnchor,
420
+ points,
421
+ score: [
422
+ bends,
423
+ facingPenalty(sourceAnchor, sourceCenter, targetCenter) + facingPenalty(targetAnchor, targetCenter, sourceCenter),
424
+ stickinessPenalty(sourceAnchor, targetAnchor, preferred),
425
+ preferredRouteAxis && axisOf(sourceAnchor) !== preferredRouteAxis ? 1 : 0,
426
+ totalLength(path),
427
+ anchorRank[sourceAnchor],
428
+ anchorRank[targetAnchor]
429
+ ]
430
+ };
431
+ };
432
+ var candidateOrder = (left, right) => {
433
+ for (let index = 0; index < left.score.length; index += 1) {
434
+ const delta = left.score[index] - right.score[index];
435
+ if (delta !== 0) {
436
+ return delta;
437
+ }
438
+ }
439
+ return 0;
440
+ };
441
+ var selectRouteCandidate = (source, target, preferred) => {
442
+ const axisPreference = preferredAxis(source, target);
443
+ let selected = null;
444
+ for (const sourceAnchor of ANCHOR_ORDER) {
445
+ for (const targetAnchor of ANCHOR_ORDER) {
446
+ const candidate = buildCandidate(source, target, sourceAnchor, targetAnchor, preferred, axisPreference);
447
+ if (!candidate) {
448
+ continue;
449
+ }
450
+ if (!selected || candidateOrder(candidate, selected) < 0) {
451
+ selected = candidate;
452
+ }
453
+ }
454
+ }
455
+ return selected;
456
+ };
457
+ var deriveAnchors = (source, target) => {
458
+ const candidate = selectRouteCandidate(source, target);
459
+ return candidate ? {
460
+ sourceAnchor: candidate.sourceAnchor,
461
+ targetAnchor: candidate.targetAnchor
462
+ } : fallbackAnchors(source, target);
463
+ };
464
+ var calculateOrthogonalRoute = (source, target, revision, preferred) => {
465
+ const candidate = selectRouteCandidate(source, target, preferred);
466
+ if (candidate) {
467
+ return {
468
+ sourceAnchor: candidate.sourceAnchor,
469
+ targetAnchor: candidate.targetAnchor,
470
+ points: candidate.points,
471
+ calculatedAtRevision: revision
472
+ };
473
+ }
474
+ const { sourceAnchor, targetAnchor } = fallbackAnchors(source, target);
475
+ const sourcePoint = getAnchorPoint(source, sourceAnchor);
476
+ const targetPoint = getAnchorPoint(target, targetAnchor);
477
+ const points = sourceAnchor === "left" || sourceAnchor === "right" ? dedupeRoutePoints([
478
+ { x: Math.round((sourcePoint.x + targetPoint.x) / 2), y: sourcePoint.y },
479
+ { x: Math.round((sourcePoint.x + targetPoint.x) / 2), y: targetPoint.y }
480
+ ]) : dedupeRoutePoints([
481
+ { x: sourcePoint.x, y: Math.round((sourcePoint.y + targetPoint.y) / 2) },
482
+ { x: targetPoint.x, y: Math.round((sourcePoint.y + targetPoint.y) / 2) }
483
+ ]);
484
+ return {
485
+ sourceAnchor,
486
+ targetAnchor,
487
+ points,
488
+ calculatedAtRevision: revision
489
+ };
490
+ };
491
+ var recalculateEdgePaths = (document, edgeIds, nextRevision) => {
492
+ const allowed = edgeIds ? new Set(edgeIds) : null;
493
+ const nodeMap = new Map(document.nodes.map((node) => [node.id, node]));
494
+ return {
495
+ ...document,
496
+ edges: document.edges.map((edge) => {
497
+ if (edge.pathType !== "orthogonal") {
498
+ return edge;
499
+ }
500
+ if (allowed && !allowed.has(edge.id)) {
501
+ return edge;
502
+ }
503
+ const source = nodeMap.get(edge.sourceNodeId);
504
+ const target = nodeMap.get(edge.targetNodeId);
505
+ if (!source || !target) {
506
+ return edge;
507
+ }
508
+ return {
509
+ ...edge,
510
+ route: calculateOrthogonalRoute(source, target, nextRevision, edge.route)
511
+ };
512
+ })
513
+ };
514
+ };
515
+
516
+ var DEFAULT_CANVAS = {
517
+ width: 1600,
518
+ height: 900,
519
+ backgroundColor: "#ffffff",
520
+ padding: 80,
521
+ autoExpand: true
522
+ };
523
+ var round = (value) => Math.round(value);
524
+ var normalizePoint = (point) => ({
525
+ x: round(point.x),
526
+ y: round(point.y)
527
+ });
528
+ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
529
+ var normalizeCanvas = (canvas) => ({
530
+ width: round(canvas?.width ?? DEFAULT_CANVAS.width),
531
+ height: round(canvas?.height ?? DEFAULT_CANVAS.height),
532
+ backgroundColor: canvas?.backgroundColor ?? DEFAULT_CANVAS.backgroundColor,
533
+ padding: round(canvas?.padding ?? DEFAULT_CANVAS.padding),
534
+ autoExpand: canvas?.autoExpand ?? DEFAULT_CANVAS.autoExpand
535
+ });
536
+ var createDocument = (input, actor, timestamp = nowIso()) => {
537
+ return normalizeDocument({
538
+ id: input?.id ?? generateDocumentId(),
539
+ title: input?.title ?? "Untitled Diagram",
540
+ revision: 1,
541
+ metadata: {
542
+ createdBy: actor.actorId,
543
+ createdAt: timestamp,
544
+ updatedAt: timestamp,
545
+ ownerId: actor.ownerId,
546
+ tenantId: actor.tenantId
547
+ },
548
+ canvas: normalizeCanvas(input?.canvas),
549
+ nodes: [],
550
+ edges: []
551
+ });
552
+ };
553
+ var normalizeNode = (input) => ({
554
+ id: input.id ?? generateNodeId(),
555
+ type: input.type,
556
+ x: round(input.x),
557
+ y: round(input.y),
558
+ width: round(input.width),
559
+ height: round(input.height),
560
+ text: input.text ?? "",
561
+ style: resolveNodeStyle(input.style, input.type === "roundedRectangle"),
562
+ zIndex: round(input.zIndex ?? 0),
563
+ metadata: { ...input.metadata ?? {} }
564
+ });
565
+ var normalizeEdge = (input, nodeMap) => {
566
+ const sourceNode = nodeMap.get(input.sourceNodeId);
567
+ const targetNode = nodeMap.get(input.targetNodeId);
568
+ const anchors = sourceNode && targetNode ? deriveAnchors(sourceNode, targetNode) : { sourceAnchor: "right", targetAnchor: "left" };
569
+ return {
570
+ id: input.id ?? generateEdgeId(),
571
+ sourceNodeId: input.sourceNodeId,
572
+ targetNodeId: input.targetNodeId,
573
+ label: input.label ?? "",
574
+ pathType: input.pathType ?? "straight",
575
+ route: {
576
+ sourceAnchor: input.route?.sourceAnchor ?? anchors.sourceAnchor,
577
+ targetAnchor: input.route?.targetAnchor ?? anchors.targetAnchor,
578
+ points: (input.route?.points ?? []).map(normalizePoint),
579
+ calculatedAtRevision: input.route?.calculatedAtRevision ?? null
580
+ },
581
+ style: resolveEdgeStyle(input.style),
582
+ metadata: { ...input.metadata ?? {} }
583
+ };
584
+ };
585
+ var autoExpandCanvas = (document) => {
586
+ if (!document.canvas.autoExpand) {
587
+ return document;
588
+ }
589
+ let maxX = document.canvas.width;
590
+ let maxY = document.canvas.height;
591
+ for (const node of document.nodes) {
592
+ maxX = Math.max(maxX, node.x + node.width + document.canvas.padding);
593
+ maxY = Math.max(maxY, node.y + node.height + document.canvas.padding);
594
+ }
595
+ for (const edge of document.edges) {
596
+ for (const point of edge.route.points) {
597
+ maxX = Math.max(maxX, point.x + document.canvas.padding);
598
+ maxY = Math.max(maxY, point.y + document.canvas.padding);
599
+ }
600
+ }
601
+ return {
602
+ ...document,
603
+ canvas: {
604
+ ...document.canvas,
605
+ width: Math.max(320, round(maxX)),
606
+ height: Math.max(240, round(maxY))
607
+ }
608
+ };
609
+ };
610
+ var normalizeDocument = (input) => {
611
+ const normalizedNodes = input.nodes.map(normalizeNode).sort((a, b) => a.zIndex - b.zIndex || a.id.localeCompare(b.id));
612
+ const nodeMap = new Map(normalizedNodes.map((node) => [node.id, node]));
613
+ const normalizedEdges = input.edges.map((edge) => normalizeEdge(edge, nodeMap)).sort((a, b) => a.id.localeCompare(b.id));
614
+ return autoExpandCanvas({
615
+ id: input.id,
616
+ title: input.title,
617
+ revision: round(input.revision),
618
+ metadata: { ...input.metadata },
619
+ canvas: normalizeCanvas(input.canvas),
620
+ nodes: normalizedNodes,
621
+ edges: normalizedEdges
622
+ });
623
+ };
624
+
625
+ var nodeMapOf = (document) => new Map(document.nodes.map((node) => [node.id, node]));
626
+ var edgeMapOf = (document) => new Map(document.edges.map((edge) => [edge.id, edge]));
627
+ var withRevision = (document, revision) => ({
628
+ ...document,
629
+ revision,
630
+ metadata: {
631
+ ...document.metadata,
632
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
633
+ }
634
+ });
635
+ var addNode = (document, input) => {
636
+ const node = normalizeNode(input);
637
+ return {
638
+ document: normalizeDocument({
639
+ ...document,
640
+ nodes: [...document.nodes, node]
641
+ }),
642
+ changedObjectIds: [node.id]
643
+ };
644
+ };
645
+ var updateNode = (document, nodeId, changes) => {
646
+ let changed = false;
647
+ const nodes = document.nodes.map((node) => {
648
+ if (node.id !== nodeId) {
649
+ return node;
650
+ }
651
+ changed = true;
652
+ return normalizeNode({
653
+ ...node,
654
+ ...changes,
655
+ metadata: changes.metadata ?? node.metadata,
656
+ style: {
657
+ ...node.style,
658
+ ...changes.style
659
+ }
660
+ });
661
+ });
662
+ return {
663
+ document: normalizeDocument({ ...document, nodes }),
664
+ changedObjectIds: changed ? [nodeId] : []
665
+ };
666
+ };
667
+ var deleteNode = (document, nodeId) => {
668
+ const edgesToDelete = document.edges.filter((edge) => edge.sourceNodeId === nodeId || edge.targetNodeId === nodeId).map((edge) => edge.id);
669
+ return {
670
+ document: normalizeDocument({
671
+ ...document,
672
+ nodes: document.nodes.filter((node) => node.id !== nodeId),
673
+ edges: document.edges.filter((edge) => !edgesToDelete.includes(edge.id))
674
+ }),
675
+ changedObjectIds: [nodeId, ...edgesToDelete]
676
+ };
677
+ };
678
+ var addEdge = (document, input) => {
679
+ const edge = normalizeEdge(input, nodeMapOf(document));
680
+ return {
681
+ document: normalizeDocument({
682
+ ...document,
683
+ edges: [...document.edges, edge]
684
+ }),
685
+ changedObjectIds: [edge.id]
686
+ };
687
+ };
688
+ var updateEdge = (document, edgeId, changes) => {
689
+ let changed = false;
690
+ const nodes = nodeMapOf(document);
691
+ const edges = document.edges.map((edge) => {
692
+ if (edge.id !== edgeId) {
693
+ return edge;
694
+ }
695
+ changed = true;
696
+ return normalizeEdge(
697
+ {
698
+ ...edge,
699
+ ...changes,
700
+ metadata: changes.metadata ?? edge.metadata,
701
+ route: {
702
+ ...edge.route,
703
+ ...changes.route
704
+ },
705
+ style: {
706
+ ...edge.style,
707
+ ...changes.style
708
+ }
709
+ },
710
+ nodes
711
+ );
712
+ });
713
+ return {
714
+ document: normalizeDocument({ ...document, edges }),
715
+ changedObjectIds: changed ? [edgeId] : []
716
+ };
717
+ };
718
+ var deleteEdge = (document, edgeId) => ({
719
+ document: normalizeDocument({
720
+ ...document,
721
+ edges: document.edges.filter((edge) => edge.id !== edgeId)
722
+ }),
723
+ changedObjectIds: [edgeId]
724
+ });
725
+ var replaceDocument = (document, replacement) => ({
726
+ document: normalizeDocument({
727
+ ...replacement,
728
+ id: document.id,
729
+ revision: document.revision,
730
+ metadata: {
731
+ ...document.metadata,
732
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
733
+ }
734
+ }),
735
+ changedObjectIds: [
736
+ replacement.id,
737
+ ...replacement.nodes.map((node) => node.id),
738
+ ...replacement.edges.map((edge) => edge.id)
739
+ ]
740
+ });
741
+ var recalculatePaths = (document, edgeIds, nextRevision) => {
742
+ const before = edgeMapOf(document);
743
+ const next = recalculateEdgePaths(document, edgeIds, nextRevision);
744
+ const changedObjectIds = next.edges.filter((edge) => JSON.stringify(before.get(edge.id)?.route) !== JSON.stringify(edge.route)).map((edge) => edge.id);
745
+ return {
746
+ document: normalizeDocument(next),
747
+ changedObjectIds
748
+ };
749
+ };
750
+ var applyPatchOperations = (document, operations, nextRevision) => {
751
+ let current = document;
752
+ const changedObjectIds = /* @__PURE__ */ new Set();
753
+ for (const operation of operations) {
754
+ let result;
755
+ switch (operation.op) {
756
+ case "addNode":
757
+ result = addNode(current, operation.node);
758
+ break;
759
+ case "updateNode":
760
+ result = updateNode(current, operation.nodeId, operation.changes);
761
+ break;
762
+ case "deleteNode":
763
+ result = deleteNode(current, operation.nodeId);
764
+ break;
765
+ case "addEdge":
766
+ result = addEdge(current, operation.edge);
767
+ break;
768
+ case "updateEdge":
769
+ result = updateEdge(current, operation.edgeId, operation.changes);
770
+ break;
771
+ case "deleteEdge":
772
+ result = deleteEdge(current, operation.edgeId);
773
+ break;
774
+ case "replaceDocument":
775
+ result = replaceDocument(current, operation.document);
776
+ break;
777
+ case "recalculateEdgePaths":
778
+ result = recalculatePaths(current, operation.edgeIds, nextRevision);
779
+ break;
780
+ }
781
+ current = result.document;
782
+ for (const objectId of result.changedObjectIds) {
783
+ changedObjectIds.add(objectId);
784
+ }
785
+ }
786
+ return {
787
+ document: current,
788
+ changedObjectIds: [...changedObjectIds]
789
+ };
790
+ };
791
+
792
+ var DEFAULT_INSECURE_DEV_AUTH_TOKEN = "local-dev-token";
793
+ var INSECURE_DEV_ACTOR = {
794
+ actorId: "local-dev",
795
+ ownerId: "local-dev",
796
+ tenantId: "local"
797
+ };
798
+ var authFailure = (code, message) => ({
799
+ success: false,
800
+ errors: [error(code, message)]
801
+ });
802
+ var resolveAuthOptions = (options = {}) => ({
803
+ tokenRegistry: options.tokenRegistry ?? {},
804
+ allowInsecureDevBypass: options.allowInsecureDevBypass ?? false,
805
+ insecureDevToken: options.insecureDevToken?.trim() || DEFAULT_INSECURE_DEV_AUTH_TOKEN
806
+ });
807
+ var resolveActorFromAuthorizationHeader = (authorizationHeader, options) => {
808
+ if (authorizationHeader === void 0) {
809
+ return authFailure("AUTH_REQUIRED", "Authorization header is required.");
810
+ }
811
+ if (Array.isArray(authorizationHeader)) {
812
+ return authFailure("INVALID_AUTH_TOKEN", "Authorization header must be a single Bearer token.");
813
+ }
814
+ const match = authorizationHeader.match(/^Bearer\s+(.+)$/i);
815
+ if (!match) {
816
+ return authFailure("INVALID_AUTH_TOKEN", "Authorization header must use Bearer <token>.");
817
+ }
818
+ const token = match[1]?.trim();
819
+ if (!token) {
820
+ return authFailure("INVALID_AUTH_TOKEN", "Authorization bearer token must not be empty.");
821
+ }
822
+ if (options.allowInsecureDevBypass && token === options.insecureDevToken) {
823
+ return {
824
+ success: true,
825
+ actor: { ...INSECURE_DEV_ACTOR }
826
+ };
827
+ }
828
+ const actor = options.tokenRegistry[token];
829
+ if (!actor) {
830
+ return authFailure("INVALID_AUTH_TOKEN", "Authorization token is not recognized.");
831
+ }
832
+ return {
833
+ success: true,
834
+ actor: { ...actor }
835
+ };
836
+ };
837
+
838
+ import Database from "better-sqlite3";
839
+ var MIGRATIONS = [
840
+ `
841
+ CREATE TABLE IF NOT EXISTS documents (
842
+ id TEXT PRIMARY KEY,
843
+ tenant_id TEXT NOT NULL,
844
+ owner_id TEXT NOT NULL,
845
+ title TEXT NOT NULL,
846
+ revision INTEGER NOT NULL,
847
+ document_json TEXT NOT NULL,
848
+ created_at TEXT NOT NULL,
849
+ updated_at TEXT NOT NULL
850
+ );
851
+ `,
852
+ `
853
+ CREATE INDEX IF NOT EXISTS idx_documents_tenant_updated
854
+ ON documents (tenant_id, updated_at DESC);
855
+ `,
856
+ `
857
+ CREATE TABLE IF NOT EXISTS document_operation_logs (
858
+ request_id TEXT PRIMARY KEY,
859
+ document_id TEXT NOT NULL,
860
+ tenant_id TEXT NOT NULL,
861
+ actor_id TEXT NOT NULL,
862
+ operation_type TEXT NOT NULL,
863
+ success INTEGER NOT NULL,
864
+ revision INTEGER,
865
+ changed_object_ids_json TEXT NOT NULL,
866
+ errors_json TEXT NOT NULL,
867
+ warnings_json TEXT NOT NULL,
868
+ created_at TEXT NOT NULL
869
+ );
870
+ `,
871
+ `
872
+ CREATE INDEX IF NOT EXISTS idx_document_operation_logs_document
873
+ ON document_operation_logs (document_id, created_at DESC);
874
+ `
875
+ ];
876
+ var isSqliteConstraintError = (cause) => cause instanceof Error && "code" in cause && typeof cause.code === "string" && cause.code.startsWith("SQLITE_CONSTRAINT");
877
+ var documentParams = (document) => ({
878
+ id: document.id,
879
+ tenant_id: document.metadata.tenantId,
880
+ owner_id: document.metadata.ownerId,
881
+ title: document.title,
882
+ revision: document.revision,
883
+ document_json: JSON.stringify(document),
884
+ created_at: document.metadata.createdAt,
885
+ updated_at: document.metadata.updatedAt
886
+ });
887
+ var DocumentAlreadyExistsError = class extends Error {
888
+ constructor(documentId) {
889
+ super(`Document already exists: ${documentId}`);
890
+ this.name = "DocumentAlreadyExistsError";
891
+ }
892
+ };
893
+ var SQLiteDocumentRepository = class {
894
+ db;
895
+ constructor(filename = ":memory:") {
896
+ this.db = new Database(filename);
897
+ this.db.pragma("foreign_keys = ON");
898
+ if (filename !== ":memory:") {
899
+ this.db.pragma("journal_mode = WAL");
900
+ }
901
+ this.db.pragma("busy_timeout = 5000");
902
+ for (const migration of MIGRATIONS) {
903
+ this.db.exec(migration);
904
+ }
905
+ }
906
+ close() {
907
+ this.db.close();
908
+ }
909
+ transaction(callback) {
910
+ return this.db.transaction(callback)();
911
+ }
912
+ getDocument(id) {
913
+ const row = this.db.prepare("SELECT document_json FROM documents WHERE id = ?").get(id);
914
+ return row ? JSON.parse(row.document_json) : null;
915
+ }
916
+ insertDocument(document) {
917
+ try {
918
+ this.db.prepare(
919
+ `
920
+ INSERT INTO documents (
921
+ id, tenant_id, owner_id, title, revision, document_json, created_at, updated_at
922
+ ) VALUES (
923
+ @id, @tenant_id, @owner_id, @title, @revision, @document_json, @created_at, @updated_at
924
+ )
925
+ `
926
+ ).run(documentParams(document));
927
+ } catch (cause) {
928
+ if (isSqliteConstraintError(cause)) {
929
+ throw new DocumentAlreadyExistsError(document.id);
930
+ }
931
+ throw cause;
932
+ }
933
+ }
934
+ updateDocument(document) {
935
+ const result = this.db.prepare(
936
+ `
937
+ UPDATE documents
938
+ SET
939
+ tenant_id = @tenant_id,
940
+ owner_id = @owner_id,
941
+ title = @title,
942
+ revision = @revision,
943
+ document_json = @document_json,
944
+ created_at = @created_at,
945
+ updated_at = @updated_at
946
+ WHERE id = @id
947
+ `
948
+ ).run(documentParams(document));
949
+ if (result.changes === 0) {
950
+ throw new Error(`Document not found for update: ${document.id}`);
951
+ }
952
+ }
953
+ insertOperationLog(entry) {
954
+ this.db.prepare(
955
+ `
956
+ INSERT INTO document_operation_logs (
957
+ request_id, document_id, tenant_id, actor_id, operation_type, success, revision,
958
+ changed_object_ids_json, errors_json, warnings_json, created_at
959
+ ) VALUES (
960
+ @request_id, @document_id, @tenant_id, @actor_id, @operation_type, @success, @revision,
961
+ @changed_object_ids_json, @errors_json, @warnings_json, @created_at
962
+ )
963
+ `
964
+ ).run({
965
+ request_id: entry.requestId,
966
+ document_id: entry.documentId,
967
+ tenant_id: entry.tenantId,
968
+ actor_id: entry.actorId,
969
+ operation_type: entry.operationType,
970
+ success: entry.success ? 1 : 0,
971
+ revision: entry.revision,
972
+ changed_object_ids_json: JSON.stringify(entry.changedObjectIds),
973
+ errors_json: JSON.stringify(entry.errors),
974
+ warnings_json: JSON.stringify(entry.warnings),
975
+ created_at: entry.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
976
+ });
977
+ }
978
+ };
979
+ var SqliteDocumentRepository = class extends SQLiteDocumentRepository {
980
+ insertDocument(document) {
981
+ super.insertDocument(document);
982
+ }
983
+ updateDocument(document) {
984
+ super.updateDocument(document);
985
+ }
986
+ logOperation(entry) {
987
+ this.insertOperationLog({
988
+ ...entry,
989
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
990
+ });
991
+ }
992
+ };
993
+
994
+ import { DOMParser } from "@xmldom/xmldom";
995
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
996
+ "mxfile",
997
+ "diagram",
998
+ "mxGraphModel",
999
+ "root",
1000
+ "mxCell",
1001
+ "mxGeometry",
1002
+ "Array",
1003
+ "mxPoint"
1004
+ ]);
1005
+ var childElements = (node) => {
1006
+ const elements = [];
1007
+ for (let index = 0; index < node.childNodes.length; index += 1) {
1008
+ const child = node.childNodes.item(index);
1009
+ if (child && child.nodeType === child.ELEMENT_NODE) {
1010
+ elements.push(child);
1011
+ }
1012
+ }
1013
+ return elements;
1014
+ };
1015
+ var getSingleChild = (node, tagName) => childElements(node).find((child) => child.tagName === tagName);
1016
+ var validateDrawioXml = (xml) => {
1017
+ const doc = new DOMParser().parseFromString(xml, "text/xml");
1018
+ const root = doc.documentElement;
1019
+ if (!root) {
1020
+ return { errors: [error("DRAWIO_INVALID_XML", "XML could not be parsed")], warnings: [] };
1021
+ }
1022
+ const errors = [];
1023
+ const visit = (element) => {
1024
+ if (!ALLOWED_TAGS.has(element.tagName)) {
1025
+ errors.push(error("DRAWIO_INVALID_TAG", `Unsupported draw.io tag: ${element.tagName}`));
1026
+ }
1027
+ for (const child of childElements(element)) {
1028
+ visit(child);
1029
+ }
1030
+ };
1031
+ visit(root);
1032
+ let graphModel;
1033
+ if (root.tagName === "mxfile") {
1034
+ const diagram = getSingleChild(root, "diagram");
1035
+ graphModel = diagram ? getSingleChild(diagram, "mxGraphModel") : void 0;
1036
+ } else if (root.tagName === "mxGraphModel") {
1037
+ graphModel = root;
1038
+ } else {
1039
+ errors.push(error("DRAWIO_INVALID_ROOT", "Root element must be mxfile or mxGraphModel"));
1040
+ }
1041
+ if (!graphModel) {
1042
+ errors.push(error("DRAWIO_INVALID_STRUCTURE", "mxGraphModel element is required"));
1043
+ return { errors, warnings: [] };
1044
+ }
1045
+ const graphRoot = getSingleChild(graphModel, "root");
1046
+ if (!graphRoot) {
1047
+ errors.push(error("DRAWIO_INVALID_STRUCTURE", "mxGraphModel must contain root"));
1048
+ return { errors, warnings: [] };
1049
+ }
1050
+ const cells = childElements(graphRoot).filter((element) => element.tagName === "mxCell");
1051
+ const cellIds = new Set(cells.map((cell) => cell.getAttribute("id")).filter(Boolean));
1052
+ if (!cellIds.has("0") || !cells.some((cell) => cell.getAttribute("id") === "1" && cell.getAttribute("parent") === "0")) {
1053
+ errors.push(error("DRAWIO_INVALID_STRUCTURE", "Missing required mxCell skeleton"));
1054
+ }
1055
+ for (const cell of cells) {
1056
+ const cellId = cell.getAttribute("id") ?? void 0;
1057
+ const isEdge = cell.getAttribute("edge") === "1";
1058
+ const parent = cell.getAttribute("parent");
1059
+ if (cellId !== "0" && parent && !cellIds.has(parent)) {
1060
+ errors.push(error("DRAWIO_REFERENCE_MISSING", "mxCell parent reference is invalid", { objectId: cellId }));
1061
+ }
1062
+ if (isEdge) {
1063
+ const source = cell.getAttribute("source");
1064
+ const target = cell.getAttribute("target");
1065
+ if (!source || !target || !parent) {
1066
+ errors.push(error("DRAWIO_INVALID_EDGE", "Edge cells require source, target, and parent", { objectId: cellId }));
1067
+ }
1068
+ if (source && !cellIds.has(source)) {
1069
+ errors.push(error("DRAWIO_REFERENCE_MISSING", "Edge source reference is invalid", { objectId: cellId }));
1070
+ }
1071
+ if (target && !cellIds.has(target)) {
1072
+ errors.push(error("DRAWIO_REFERENCE_MISSING", "Edge target reference is invalid", { objectId: cellId }));
1073
+ }
1074
+ const geometry = childElements(cell).find(
1075
+ (child) => child.tagName === "mxGeometry" && child.getAttribute("relative") === "1" && child.getAttribute("as") === "geometry"
1076
+ );
1077
+ if (!geometry) {
1078
+ errors.push(
1079
+ error("DRAWIO_INVALID_EDGE", 'Edge cells require <mxGeometry relative="1" as="geometry" />', {
1080
+ objectId: cellId
1081
+ })
1082
+ );
1083
+ }
1084
+ }
1085
+ }
1086
+ return { errors, warnings: [] };
1087
+ };
1088
+
1089
+ import { z } from "zod";
1090
+ var idPattern = (prefix) => new RegExp(`^${prefix}[a-z0-9][a-z0-9_-]{2,63}$`);
1091
+ var literalUnion = (values) => z.union(values.map((value) => z.literal(value)));
1092
+ var hexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, "Expected six-digit hex color");
1093
+ var metadataSchema = z.record(z.string(), z.string());
1094
+ var documentMetadataSchema = z.object({
1095
+ createdBy: z.string().min(1).max(64),
1096
+ createdAt: z.string().datetime(),
1097
+ updatedAt: z.string().datetime(),
1098
+ ownerId: z.string().min(1).max(64),
1099
+ tenantId: z.string().min(1).max(64)
1100
+ }).strict();
1101
+ var canvasSchema = z.object({
1102
+ width: z.number().int().min(320).max(1e4),
1103
+ height: z.number().int().min(240).max(1e4),
1104
+ backgroundColor: hexColorSchema,
1105
+ padding: z.number().int().min(0).max(400),
1106
+ autoExpand: z.boolean()
1107
+ }).strict();
1108
+ var nodeStyleInputSchema = z.object({
1109
+ preset: z.enum(STYLE_PRESETS).optional(),
1110
+ fillColor: hexColorSchema.optional(),
1111
+ strokeColor: hexColorSchema.optional(),
1112
+ strokeWidth: literalUnion(STROKE_WIDTHS).optional(),
1113
+ dashed: z.boolean().optional(),
1114
+ rounded: z.boolean().optional(),
1115
+ textColor: hexColorSchema.optional(),
1116
+ fontSize: literalUnion(FONT_SIZES).optional(),
1117
+ fontWeight: z.enum(FONT_WEIGHTS).optional(),
1118
+ textAlign: z.enum(TEXT_ALIGNS).optional(),
1119
+ verticalAlign: z.enum(VERTICAL_ALIGNS).optional(),
1120
+ wrap: z.boolean().optional()
1121
+ }).strict();
1122
+ var edgeStyleInputSchema = z.object({
1123
+ preset: z.enum(STYLE_PRESETS).optional(),
1124
+ strokeColor: hexColorSchema.optional(),
1125
+ strokeWidth: literalUnion(STROKE_WIDTHS).optional(),
1126
+ dashed: z.boolean().optional(),
1127
+ textColor: hexColorSchema.optional(),
1128
+ fontSize: literalUnion(EDGE_FONT_SIZES).optional(),
1129
+ fontWeight: z.enum(FONT_WEIGHTS).optional(),
1130
+ textAlign: z.enum(TEXT_ALIGNS).optional(),
1131
+ verticalAlign: z.enum(VERTICAL_ALIGNS).optional(),
1132
+ wrap: z.boolean().optional(),
1133
+ arrowStart: z.enum(ARROW_TYPES).optional(),
1134
+ arrowEnd: z.enum(ARROW_TYPES).optional()
1135
+ }).strict();
1136
+ var resolvedNodeStyleSchema = nodeStyleInputSchema.extend({
1137
+ preset: z.enum(STYLE_PRESETS),
1138
+ fillColor: hexColorSchema,
1139
+ strokeColor: hexColorSchema,
1140
+ strokeWidth: literalUnion(STROKE_WIDTHS),
1141
+ dashed: z.boolean(),
1142
+ rounded: z.boolean(),
1143
+ textColor: hexColorSchema,
1144
+ fontSize: literalUnion(FONT_SIZES),
1145
+ fontWeight: z.enum(FONT_WEIGHTS),
1146
+ textAlign: z.enum(TEXT_ALIGNS),
1147
+ verticalAlign: z.enum(VERTICAL_ALIGNS),
1148
+ wrap: z.boolean()
1149
+ });
1150
+ var resolvedEdgeStyleSchema = edgeStyleInputSchema.extend({
1151
+ preset: z.enum(STYLE_PRESETS),
1152
+ strokeColor: hexColorSchema,
1153
+ strokeWidth: literalUnion(STROKE_WIDTHS),
1154
+ dashed: z.boolean(),
1155
+ textColor: hexColorSchema,
1156
+ fontSize: literalUnion(EDGE_FONT_SIZES),
1157
+ fontWeight: z.enum(FONT_WEIGHTS),
1158
+ textAlign: z.enum(TEXT_ALIGNS),
1159
+ verticalAlign: z.enum(VERTICAL_ALIGNS),
1160
+ wrap: z.boolean(),
1161
+ arrowStart: z.enum(ARROW_TYPES),
1162
+ arrowEnd: z.enum(ARROW_TYPES)
1163
+ });
1164
+ var pointSchema = z.object({
1165
+ x: z.number().int(),
1166
+ y: z.number().int()
1167
+ }).strict();
1168
+ var edgeRouteSchema = z.object({
1169
+ points: z.array(pointSchema),
1170
+ sourceAnchor: z.enum(ANCHOR_SIDES),
1171
+ targetAnchor: z.enum(ANCHOR_SIDES),
1172
+ calculatedAtRevision: z.number().int().nullable()
1173
+ }).strict();
1174
+ var nodeInputSchema = z.object({
1175
+ id: z.string().regex(idPattern(NODE_ID_PREFIX)).optional(),
1176
+ type: z.enum(NODE_TYPES),
1177
+ x: z.number().int().min(0).max(1e5),
1178
+ y: z.number().int().min(0).max(1e5),
1179
+ width: z.number().int().min(16).max(4e3),
1180
+ height: z.number().int().min(16).max(4e3),
1181
+ text: z.string().max(4e3).default(""),
1182
+ style: nodeStyleInputSchema.optional(),
1183
+ zIndex: z.number().int().min(0).max(9999).default(0),
1184
+ metadata: metadataSchema.default({})
1185
+ }).strict();
1186
+ var nodeChangesSchema = z.object({
1187
+ type: z.enum(NODE_TYPES).optional(),
1188
+ x: z.number().int().min(0).max(1e5).optional(),
1189
+ y: z.number().int().min(0).max(1e5).optional(),
1190
+ width: z.number().int().min(16).max(4e3).optional(),
1191
+ height: z.number().int().min(16).max(4e3).optional(),
1192
+ text: z.string().max(4e3).optional(),
1193
+ style: nodeStyleInputSchema.optional(),
1194
+ zIndex: z.number().int().min(0).max(9999).optional(),
1195
+ metadata: metadataSchema.optional()
1196
+ }).strict();
1197
+ var edgeInputSchema = z.object({
1198
+ id: z.string().regex(idPattern(EDGE_ID_PREFIX)).optional(),
1199
+ sourceNodeId: z.string().regex(idPattern(NODE_ID_PREFIX)),
1200
+ targetNodeId: z.string().regex(idPattern(NODE_ID_PREFIX)),
1201
+ label: z.string().max(1e3).default(""),
1202
+ pathType: z.enum(EDGE_PATH_TYPES).default("straight"),
1203
+ route: edgeRouteSchema.partial().optional(),
1204
+ style: edgeStyleInputSchema.optional(),
1205
+ metadata: metadataSchema.default({})
1206
+ }).strict();
1207
+ var edgeChangesSchema = z.object({
1208
+ sourceNodeId: z.string().regex(idPattern(NODE_ID_PREFIX)).optional(),
1209
+ targetNodeId: z.string().regex(idPattern(NODE_ID_PREFIX)).optional(),
1210
+ label: z.string().max(1e3).optional(),
1211
+ pathType: z.enum(EDGE_PATH_TYPES).optional(),
1212
+ route: edgeRouteSchema.partial().optional(),
1213
+ style: edgeStyleInputSchema.optional(),
1214
+ metadata: metadataSchema.optional()
1215
+ }).strict();
1216
+ var nodeSchema = nodeInputSchema.extend({
1217
+ id: z.string().regex(idPattern(NODE_ID_PREFIX)),
1218
+ style: resolvedNodeStyleSchema
1219
+ });
1220
+ var edgeSchema = edgeInputSchema.extend({
1221
+ id: z.string().regex(idPattern(EDGE_ID_PREFIX)),
1222
+ route: edgeRouteSchema,
1223
+ style: resolvedEdgeStyleSchema
1224
+ });
1225
+ var documentSchema = z.object({
1226
+ id: z.string().regex(idPattern(DOCUMENT_ID_PREFIX)),
1227
+ title: z.string().min(1).max(120),
1228
+ revision: z.number().int().min(1),
1229
+ metadata: documentMetadataSchema,
1230
+ canvas: canvasSchema,
1231
+ nodes: z.array(nodeSchema),
1232
+ edges: z.array(edgeSchema)
1233
+ }).strict();
1234
+ var createDocumentRequestSchema = z.object({
1235
+ id: z.string().regex(idPattern(DOCUMENT_ID_PREFIX)).optional(),
1236
+ title: z.string().min(1).max(120).optional(),
1237
+ canvas: canvasSchema.partial().optional()
1238
+ }).strict();
1239
+ var baseRevisionSchema = z.object({
1240
+ baseRevision: z.number().int().min(1)
1241
+ });
1242
+ var addNodeRequestSchema = z.object({
1243
+ baseRevision: z.number().int().min(1),
1244
+ node: nodeInputSchema
1245
+ }).strict();
1246
+ var updateNodeRequestSchema = z.object({
1247
+ baseRevision: z.number().int().min(1),
1248
+ changes: nodeChangesSchema
1249
+ }).strict();
1250
+ var addEdgeRequestSchema = z.object({
1251
+ baseRevision: z.number().int().min(1),
1252
+ edge: edgeInputSchema
1253
+ }).strict();
1254
+ var updateEdgeRequestSchema = z.object({
1255
+ baseRevision: z.number().int().min(1),
1256
+ changes: edgeChangesSchema
1257
+ }).strict();
1258
+ var cloneDocumentRequestSchema = z.object({
1259
+ title: z.string().min(1).max(120).optional()
1260
+ }).strict();
1261
+ var patchOperationSchema = z.discriminatedUnion("op", [
1262
+ z.object({ op: z.literal("addNode"), node: nodeInputSchema }).strict(),
1263
+ z.object({ op: z.literal("updateNode"), nodeId: z.string().regex(idPattern(NODE_ID_PREFIX)), changes: nodeChangesSchema }).strict(),
1264
+ z.object({ op: z.literal("deleteNode"), nodeId: z.string().regex(idPattern(NODE_ID_PREFIX)) }).strict(),
1265
+ z.object({ op: z.literal("addEdge"), edge: edgeInputSchema }).strict(),
1266
+ z.object({ op: z.literal("updateEdge"), edgeId: z.string().regex(idPattern(EDGE_ID_PREFIX)), changes: edgeChangesSchema }).strict(),
1267
+ z.object({ op: z.literal("deleteEdge"), edgeId: z.string().regex(idPattern(EDGE_ID_PREFIX)) }).strict(),
1268
+ z.object({ op: z.literal("replaceDocument"), document: documentSchema }).strict(),
1269
+ z.object({
1270
+ op: z.literal("recalculateEdgePaths"),
1271
+ edgeIds: z.array(z.string().regex(idPattern(EDGE_ID_PREFIX))).optional()
1272
+ }).strict()
1273
+ ]);
1274
+ var patchRequestSchema = z.object({
1275
+ baseRevision: z.number().int().min(1),
1276
+ atomic: z.boolean().default(true),
1277
+ dryRun: z.boolean().default(false),
1278
+ operations: z.array(patchOperationSchema).min(1)
1279
+ }).strict();
1280
+ var validateRequestSchema = z.object({
1281
+ target: z.enum(["document", "drawio"]).default("document"),
1282
+ drawioMode: z.enum(["mxfile", "mxGraphModel"]).default("mxfile")
1283
+ }).strict();
1284
+ var renderRequestSchema = z.object({
1285
+ includeObjectMetadata: z.boolean().default(true),
1286
+ fitToCanvas: z.boolean().default(true)
1287
+ }).strict();
1288
+ var exportRequestSchema = z.object({
1289
+ format: z.enum(["drawio", "svg", "png"]).default("drawio"),
1290
+ drawioMode: z.enum(["mxfile", "mxGraphModel"]).default("mxfile"),
1291
+ scale: z.union([z.literal(1), z.literal(2), z.literal(3)]).default(1)
1292
+ }).strict();
1293
+ var issuePath = (issue) => issue.path.join(".");
1294
+ var mapIssue = (issue) => {
1295
+ const path = issuePath(issue);
1296
+ const isStylePath = path.includes("style");
1297
+ if (issue.code === "unrecognized_keys") {
1298
+ return issue.keys.map(
1299
+ (key) => error(
1300
+ isStylePath ? "INVALID_STYLE_KEY" : "INVALID_INPUT",
1301
+ `Unknown property: ${key}`,
1302
+ { fieldPath: path ? `${path}.${key}` : key }
1303
+ )
1304
+ );
1305
+ }
1306
+ if (issue.code === "invalid_value") {
1307
+ if (path.endsWith("type")) {
1308
+ return [error("UNSUPPORTED_NODE_TYPE", "Unsupported node type", { fieldPath: path })];
1309
+ }
1310
+ if (path.endsWith("pathType")) {
1311
+ return [error("UNSUPPORTED_EDGE_PATH", "Unsupported edge path type", { fieldPath: path })];
1312
+ }
1313
+ return [error(isStylePath ? "INVALID_STYLE" : "INVALID_INPUT", issue.message, { fieldPath: path })];
1314
+ }
1315
+ if (issue.code === "invalid_format" && path.toLowerCase().includes("color")) {
1316
+ return [error("INVALID_COLOR", issue.message, { fieldPath: path })];
1317
+ }
1318
+ if (issue.code === "too_small" || issue.code === "too_big") {
1319
+ const code = path.endsWith("x") || path.endsWith("y") || path.endsWith("width") || path.endsWith("height") || path.endsWith("padding") ? "INVALID_GEOMETRY" : isStylePath ? "INVALID_STYLE" : "INVALID_INPUT";
1320
+ return [error(code, issue.message, { fieldPath: path })];
1321
+ }
1322
+ return [
1323
+ error(isStylePath ? "INVALID_STYLE" : "INVALID_INPUT", issue.message, {
1324
+ ...path ? { fieldPath: path } : {}
1325
+ })
1326
+ ];
1327
+ };
1328
+ var safeParseWithErrors = (schema, input) => {
1329
+ const result = schema.safeParse(input);
1330
+ if (result.success) {
1331
+ return result;
1332
+ }
1333
+ return {
1334
+ success: false,
1335
+ errors: result.error.issues.flatMap(mapIssue)
1336
+ };
1337
+ };
1338
+ var STYLE_ALLOWLISTS = {
1339
+ node: new Set(NODE_STYLE_KEYS),
1340
+ edge: new Set(EDGE_STYLE_KEYS)
1341
+ };
1342
+
1343
+ var lineHeightMultiplier = 1.2;
1344
+ var nodeOverlapThreshold = 0.25;
1345
+ var minimumMeaningfulOverlapArea = 400;
1346
+ var lineHeight = (fontSize) => Math.round(fontSize * lineHeightMultiplier);
1347
+ var intersectionArea = (a, b) => {
1348
+ const x = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
1349
+ const y = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
1350
+ return x * y;
1351
+ };
1352
+ var splitLongWord = (word, maxChars) => {
1353
+ const parts = [];
1354
+ for (let index = 0; index < word.length; index += maxChars) {
1355
+ parts.push(word.slice(index, index + maxChars));
1356
+ }
1357
+ return parts;
1358
+ };
1359
+ var wrapText = (text, maxWidth, fontSize, wrap) => {
1360
+ const maxChars = Math.max(1, Math.floor(maxWidth / (fontSize * 0.56)));
1361
+ return text.split("\n").flatMap((paragraph) => {
1362
+ if (!wrap) {
1363
+ return [paragraph];
1364
+ }
1365
+ const words = paragraph.split(/\s+/).filter(Boolean);
1366
+ if (words.length === 0) {
1367
+ return [""];
1368
+ }
1369
+ const lines = [];
1370
+ let current = "";
1371
+ for (const word of words) {
1372
+ const candidates = word.length > maxChars ? splitLongWord(word, maxChars) : [word];
1373
+ for (const candidate of candidates) {
1374
+ const next = current ? `${current} ${candidate}` : candidate;
1375
+ if (next.length <= maxChars) {
1376
+ current = next;
1377
+ } else {
1378
+ if (current) {
1379
+ lines.push(current);
1380
+ }
1381
+ current = candidate;
1382
+ }
1383
+ }
1384
+ }
1385
+ if (current) {
1386
+ lines.push(current);
1387
+ }
1388
+ return lines.length > 0 ? lines : [""];
1389
+ });
1390
+ };
1391
+ var detectTextOverflow = (node) => {
1392
+ if (!node.text.trim()) {
1393
+ return false;
1394
+ }
1395
+ const availableWidth = Math.max(1, node.width - (node.type === "text" ? 0 : 24));
1396
+ const lines = wrapText(node.text, availableWidth, node.style.fontSize, node.style.wrap);
1397
+ const maxLines = Math.max(1, Math.floor((node.height - 12) / lineHeight(node.style.fontSize)));
1398
+ return lines.length > maxLines;
1399
+ };
1400
+ var dedupeWarnings = (warnings) => {
1401
+ const seen = /* @__PURE__ */ new Set();
1402
+ return warnings.filter((item) => {
1403
+ const key = `${item.code}:${item.objectId ?? ""}:${item.fieldPath ?? ""}`;
1404
+ if (seen.has(key)) {
1405
+ return false;
1406
+ }
1407
+ seen.add(key);
1408
+ return true;
1409
+ });
1410
+ };
1411
+ var validateNodeWarnings = (document) => {
1412
+ const warnings = [];
1413
+ for (const node of document.nodes) {
1414
+ if (node.x < 0 || node.y < 0 || node.x + node.width > document.canvas.width || node.y + node.height > document.canvas.height) {
1415
+ warnings.push(
1416
+ warning("OBJECT_OUTSIDE_CANVAS", "Object exceeds canvas bounds", { objectId: node.id })
1417
+ );
1418
+ }
1419
+ if (detectTextOverflow(node)) {
1420
+ warnings.push(warning("TEXT_OVERFLOW", "Text may overflow the node bounds", { objectId: node.id }));
1421
+ }
1422
+ }
1423
+ for (let index = 0; index < document.nodes.length; index += 1) {
1424
+ for (let otherIndex = index + 1; otherIndex < document.nodes.length; otherIndex += 1) {
1425
+ const left = document.nodes[index];
1426
+ const right = document.nodes[otherIndex];
1427
+ if (!left || !right) {
1428
+ continue;
1429
+ }
1430
+ const overlapArea = intersectionArea(left, right);
1431
+ if (overlapArea === 0) {
1432
+ continue;
1433
+ }
1434
+ if (overlapArea < minimumMeaningfulOverlapArea) {
1435
+ continue;
1436
+ }
1437
+ const smallerArea = Math.min(left.width * left.height, right.width * right.height);
1438
+ if (smallerArea > 0 && overlapArea / smallerArea >= nodeOverlapThreshold) {
1439
+ warnings.push(
1440
+ warning("HIGH_NODE_OVERLAP", "Nodes overlap significantly", {
1441
+ objectId: [left.id, right.id].sort().join(",")
1442
+ })
1443
+ );
1444
+ }
1445
+ }
1446
+ }
1447
+ return warnings;
1448
+ };
1449
+ var validateEdgeWarnings = (edges, nodeIds) => edges.flatMap(
1450
+ (edge) => edge.pathType === "orthogonal" && edge.route.points.length === 0 && nodeIds.has(edge.sourceNodeId) && nodeIds.has(edge.targetNodeId) ? [warning("ORTHOGONAL_ROUTE_MISSING", "Orthogonal route has not been calculated", { objectId: edge.id })] : []
1451
+ );
1452
+ var validateDocumentSemantics = (document) => {
1453
+ const errors = [];
1454
+ const seenIds = /* @__PURE__ */ new Set();
1455
+ const nodeIds = new Set(document.nodes.map((node) => node.id));
1456
+ const allIds = [
1457
+ document.id,
1458
+ ...document.nodes.map((node) => node.id),
1459
+ ...document.edges.map((edge) => edge.id)
1460
+ ];
1461
+ for (const id of allIds) {
1462
+ if (seenIds.has(id)) {
1463
+ errors.push(error("DUPLICATE_OBJECT_ID", "Object ID must be unique across the document", { objectId: id }));
1464
+ }
1465
+ seenIds.add(id);
1466
+ }
1467
+ for (const node of document.nodes) {
1468
+ if (node.width <= 0 || node.height <= 0) {
1469
+ errors.push(error("INVALID_GEOMETRY", "Node width and height must be positive", { objectId: node.id }));
1470
+ }
1471
+ }
1472
+ for (const edge of document.edges) {
1473
+ if (!nodeIds.has(edge.sourceNodeId) || !nodeIds.has(edge.targetNodeId)) {
1474
+ errors.push(
1475
+ error("EDGE_ENDPOINT_MISSING", "Edge endpoints must reference existing nodes", { objectId: edge.id })
1476
+ );
1477
+ }
1478
+ }
1479
+ return {
1480
+ errors,
1481
+ warnings: dedupeWarnings([...validateNodeWarnings(document), ...validateEdgeWarnings(document.edges, nodeIds)])
1482
+ };
1483
+ };
1484
+
1485
+ var statusForErrors = (errors) => {
1486
+ if (errors.some((item) => item.code === "AUTH_REQUIRED" || item.code === "INVALID_AUTH_TOKEN")) {
1487
+ return 401;
1488
+ }
1489
+ if (errors.some((item) => item.code === "REVISION_CONFLICT" || item.code === "DOCUMENT_ALREADY_EXISTS")) {
1490
+ return 409;
1491
+ }
1492
+ if (errors.some((item) => item.code.endsWith("_NOT_FOUND"))) {
1493
+ return 404;
1494
+ }
1495
+ if (errors.some((item) => item.code === "FORBIDDEN")) {
1496
+ return 403;
1497
+ }
1498
+ return 400;
1499
+ };
1500
+ var sendEnvelope = (reply, envelope) => envelope.success ? reply.code(200).send(envelope) : reply.code(statusForErrors(envelope.errors)).send(envelope);
1501
+ var failureEnvelope = (requestId, errors) => ({
1502
+ requestId,
1503
+ success: false,
1504
+ revision: null,
1505
+ errors,
1506
+ warnings: [],
1507
+ changedObjectIds: []
1508
+ });
1509
+ var parseOrReply = (reply, schema, body, requestId) => {
1510
+ const parsed = safeParseWithErrors(schema, body);
1511
+ if (parsed.success) {
1512
+ return parsed.data;
1513
+ }
1514
+ reply.code(statusForErrors(parsed.errors)).send(failureEnvelope(requestId, parsed.errors));
1515
+ return void 0;
1516
+ };
1517
+ var actorFromRequest = (request) => request.drowaiActor;
1518
+ var registerDocumentRoutes = (app, { documentService, auth }) => {
1519
+ app.addHook("preHandler", async (request, reply) => {
1520
+ const resolved = resolveActorFromAuthorizationHeader(request.headers.authorization, auth);
1521
+ if (!resolved.success) {
1522
+ return reply.code(statusForErrors(resolved.errors)).send(failureEnvelope(request.id, resolved.errors));
1523
+ }
1524
+ request.drowaiActor = resolved.actor;
1525
+ return void 0;
1526
+ });
1527
+ app.post("/documents", async (request, reply) => {
1528
+ const parsed = parseOrReply(reply, createDocumentRequestSchema, request.body, request.id);
1529
+ if (!parsed) {
1530
+ return;
1531
+ }
1532
+ return sendEnvelope(reply, documentService.createDocument(request.id, actorFromRequest(request), parsed));
1533
+ });
1534
+ app.get("/documents/:id", async (request, reply) => {
1535
+ const params = request.params;
1536
+ return sendEnvelope(reply, documentService.getDocument(request.id, actorFromRequest(request), params.id));
1537
+ });
1538
+ app.post("/documents/:id/clone", async (request, reply) => {
1539
+ const params = request.params;
1540
+ const parsed = parseOrReply(reply, cloneDocumentRequestSchema, request.body ?? {}, request.id);
1541
+ if (!parsed) {
1542
+ return;
1543
+ }
1544
+ return sendEnvelope(reply, documentService.cloneDocument(request.id, actorFromRequest(request), params.id, parsed.title));
1545
+ });
1546
+ app.post("/documents/:id/nodes", async (request, reply) => {
1547
+ const params = request.params;
1548
+ const parsed = parseOrReply(reply, addNodeRequestSchema, request.body, request.id);
1549
+ if (!parsed) {
1550
+ return;
1551
+ }
1552
+ return sendEnvelope(
1553
+ reply,
1554
+ documentService.addNode(request.id, actorFromRequest(request), params.id, parsed.baseRevision, parsed.node)
1555
+ );
1556
+ });
1557
+ app.patch("/documents/:id/nodes/:nodeId", async (request, reply) => {
1558
+ const params = request.params;
1559
+ const parsed = parseOrReply(reply, updateNodeRequestSchema, request.body, request.id);
1560
+ if (!parsed) {
1561
+ return;
1562
+ }
1563
+ return sendEnvelope(
1564
+ reply,
1565
+ documentService.updateNode(
1566
+ request.id,
1567
+ actorFromRequest(request),
1568
+ params.id,
1569
+ parsed.baseRevision,
1570
+ params.nodeId,
1571
+ parsed.changes
1572
+ )
1573
+ );
1574
+ });
1575
+ app.delete("/documents/:id/nodes/:nodeId", async (request, reply) => {
1576
+ const params = request.params;
1577
+ const parsed = parseOrReply(reply, baseRevisionSchema, request.body, request.id);
1578
+ if (!parsed) {
1579
+ return;
1580
+ }
1581
+ return sendEnvelope(
1582
+ reply,
1583
+ documentService.deleteNode(
1584
+ request.id,
1585
+ actorFromRequest(request),
1586
+ params.id,
1587
+ parsed.baseRevision,
1588
+ params.nodeId
1589
+ )
1590
+ );
1591
+ });
1592
+ app.post("/documents/:id/edges", async (request, reply) => {
1593
+ const params = request.params;
1594
+ const parsed = parseOrReply(reply, addEdgeRequestSchema, request.body, request.id);
1595
+ if (!parsed) {
1596
+ return;
1597
+ }
1598
+ return sendEnvelope(
1599
+ reply,
1600
+ documentService.addEdge(request.id, actorFromRequest(request), params.id, parsed.baseRevision, parsed.edge)
1601
+ );
1602
+ });
1603
+ app.patch("/documents/:id/edges/:edgeId", async (request, reply) => {
1604
+ const params = request.params;
1605
+ const parsed = parseOrReply(reply, updateEdgeRequestSchema, request.body, request.id);
1606
+ if (!parsed) {
1607
+ return;
1608
+ }
1609
+ return sendEnvelope(
1610
+ reply,
1611
+ documentService.updateEdge(
1612
+ request.id,
1613
+ actorFromRequest(request),
1614
+ params.id,
1615
+ parsed.baseRevision,
1616
+ params.edgeId,
1617
+ parsed.changes
1618
+ )
1619
+ );
1620
+ });
1621
+ app.delete("/documents/:id/edges/:edgeId", async (request, reply) => {
1622
+ const params = request.params;
1623
+ const parsed = parseOrReply(reply, baseRevisionSchema, request.body, request.id);
1624
+ if (!parsed) {
1625
+ return;
1626
+ }
1627
+ return sendEnvelope(
1628
+ reply,
1629
+ documentService.deleteEdge(
1630
+ request.id,
1631
+ actorFromRequest(request),
1632
+ params.id,
1633
+ parsed.baseRevision,
1634
+ params.edgeId
1635
+ )
1636
+ );
1637
+ });
1638
+ app.post("/documents/:id/patch", async (request, reply) => {
1639
+ const params = request.params;
1640
+ const parsed = parseOrReply(reply, patchRequestSchema, request.body, request.id);
1641
+ if (!parsed) {
1642
+ return;
1643
+ }
1644
+ return sendEnvelope(
1645
+ reply,
1646
+ documentService.patchDocument(
1647
+ request.id,
1648
+ actorFromRequest(request),
1649
+ params.id,
1650
+ parsed.baseRevision,
1651
+ parsed.operations,
1652
+ parsed.atomic,
1653
+ parsed.dryRun
1654
+ )
1655
+ );
1656
+ });
1657
+ app.post("/documents/:id/validate", async (request, reply) => {
1658
+ const params = request.params;
1659
+ const parsed = parseOrReply(reply, validateRequestSchema, request.body ?? {}, request.id);
1660
+ if (!parsed) {
1661
+ return;
1662
+ }
1663
+ return sendEnvelope(
1664
+ reply,
1665
+ documentService.validateDocument(
1666
+ request.id,
1667
+ actorFromRequest(request),
1668
+ params.id,
1669
+ parsed.target,
1670
+ parsed.drawioMode
1671
+ )
1672
+ );
1673
+ });
1674
+ app.post("/documents/:id/render", async (request, reply) => {
1675
+ const params = request.params;
1676
+ const parsed = parseOrReply(reply, renderRequestSchema, request.body ?? {}, request.id);
1677
+ if (!parsed) {
1678
+ return;
1679
+ }
1680
+ return sendEnvelope(reply, documentService.renderDocument(request.id, actorFromRequest(request), params.id));
1681
+ });
1682
+ app.post("/documents/:id/export", async (request, reply) => {
1683
+ const params = request.params;
1684
+ const parsed = parseOrReply(reply, exportRequestSchema, request.body ?? {}, request.id);
1685
+ if (!parsed) {
1686
+ return;
1687
+ }
1688
+ return sendEnvelope(
1689
+ reply,
1690
+ documentService.exportDocument(
1691
+ request.id,
1692
+ actorFromRequest(request),
1693
+ params.id,
1694
+ parsed.format,
1695
+ parsed.drawioMode,
1696
+ parsed.scale
1697
+ )
1698
+ );
1699
+ });
1700
+ };
1701
+
1702
+ import { create } from "xmlbuilder2";
1703
+ var DEFAULT_OPTIONS = {
1704
+ mode: "mxfile",
1705
+ modifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
1706
+ host: "drowai",
1707
+ agent: "DrowAI"
1708
+ };
1709
+ var serializeStyle = (tokens) => tokens.join(";") + ";";
1710
+ var nodeStyle = (node) => {
1711
+ const shapeTokens = node.type === "rectangle" ? ["shape=rectangle", "rounded=0", "whiteSpace=wrap", "html=1"] : node.type === "roundedRectangle" ? ["shape=rectangle", "rounded=1", "whiteSpace=wrap", "html=1"] : node.type === "ellipse" ? ["ellipse", "whiteSpace=wrap", "html=1"] : node.type === "diamond" ? ["rhombus", "whiteSpace=wrap", "html=1"] : ["text", "whiteSpace=wrap", "html=1", "strokeColor=none", "fillColor=none"];
1712
+ const commonTokens = node.type === "text" ? [
1713
+ `fontColor=${node.style.textColor}`,
1714
+ `fontSize=${node.style.fontSize}`,
1715
+ `fontStyle=${node.style.fontWeight === "bold" ? 1 : 0}`,
1716
+ `align=${node.style.textAlign}`,
1717
+ `verticalAlign=${node.style.verticalAlign}`
1718
+ ] : [
1719
+ `fillColor=${node.style.fillColor}`,
1720
+ `strokeColor=${node.style.strokeColor}`,
1721
+ `strokeWidth=${node.style.strokeWidth}`,
1722
+ `dashed=${node.style.dashed ? 1 : 0}`,
1723
+ `fontColor=${node.style.textColor}`,
1724
+ `fontSize=${node.style.fontSize}`,
1725
+ `fontStyle=${node.style.fontWeight === "bold" ? 1 : 0}`,
1726
+ `align=${node.style.textAlign}`,
1727
+ `verticalAlign=${node.style.verticalAlign}`
1728
+ ];
1729
+ return serializeStyle([...shapeTokens, ...commonTokens]);
1730
+ };
1731
+ var edgeStyle = (edge) => {
1732
+ const baseTokens = edge.pathType === "orthogonal" ? ["edgeStyle=orthogonalEdgeStyle", "rounded=0", "jettySize=auto", "html=1"] : ["edgeStyle=none", "rounded=0", "html=1"];
1733
+ return serializeStyle([
1734
+ ...baseTokens,
1735
+ `strokeColor=${edge.style.strokeColor}`,
1736
+ `strokeWidth=${edge.style.strokeWidth}`,
1737
+ `dashed=${edge.style.dashed ? 1 : 0}`,
1738
+ `fontColor=${edge.style.textColor}`,
1739
+ `fontSize=${edge.style.fontSize}`,
1740
+ `fontStyle=${edge.style.fontWeight === "bold" ? 1 : 0}`,
1741
+ `align=${edge.style.textAlign}`,
1742
+ `verticalAlign=${edge.style.verticalAlign}`,
1743
+ `startArrow=${edge.style.arrowStart}`,
1744
+ `endArrow=${edge.style.arrowEnd}`
1745
+ ]);
1746
+ };
1747
+ var appendNodeCell = (root, node) => {
1748
+ root.ele("mxCell", {
1749
+ id: node.id,
1750
+ value: node.text,
1751
+ style: nodeStyle(node),
1752
+ vertex: "1",
1753
+ parent: "1"
1754
+ }).ele("mxGeometry", {
1755
+ x: String(node.x),
1756
+ y: String(node.y),
1757
+ width: String(node.width),
1758
+ height: String(node.height),
1759
+ as: "geometry"
1760
+ }).up().up();
1761
+ };
1762
+ var appendEdgeCell = (root, edge) => {
1763
+ const cell = root.ele("mxCell", {
1764
+ id: edge.id,
1765
+ value: edge.label,
1766
+ style: edgeStyle(edge),
1767
+ edge: "1",
1768
+ parent: "1",
1769
+ source: edge.sourceNodeId,
1770
+ target: edge.targetNodeId
1771
+ });
1772
+ const geometry = cell.ele("mxGeometry", {
1773
+ relative: "1",
1774
+ as: "geometry"
1775
+ });
1776
+ if (edge.route.points.length > 0) {
1777
+ const points = geometry.ele("Array", { as: "points" });
1778
+ for (const point of edge.route.points) {
1779
+ points.ele("mxPoint", {
1780
+ x: String(point.x),
1781
+ y: String(point.y)
1782
+ });
1783
+ }
1784
+ }
1785
+ };
1786
+ var buildGraphModel = (document) => {
1787
+ const graphModel = create({ version: "1.0", encoding: "UTF-8" }).ele("mxGraphModel", {
1788
+ dx: String(document.canvas.width),
1789
+ dy: String(document.canvas.height),
1790
+ grid: "1",
1791
+ gridSize: "10",
1792
+ page: "1",
1793
+ pageWidth: String(document.canvas.width),
1794
+ pageHeight: String(document.canvas.height)
1795
+ });
1796
+ const root = graphModel.ele("root");
1797
+ root.ele("mxCell", { id: "0" });
1798
+ root.ele("mxCell", { id: "1", parent: "0" });
1799
+ for (const node of document.nodes) {
1800
+ appendNodeCell(root, node);
1801
+ }
1802
+ for (const edge of document.edges) {
1803
+ appendEdgeCell(root, edge);
1804
+ }
1805
+ return graphModel;
1806
+ };
1807
+ var exportDrawioDocument = (document, options = {}) => {
1808
+ const resolved = {
1809
+ ...DEFAULT_OPTIONS,
1810
+ ...options,
1811
+ modifiedAt: options.modifiedAt ?? document.metadata.updatedAt
1812
+ };
1813
+ const graphModel = buildGraphModel(document);
1814
+ if (resolved.mode === "mxGraphModel") {
1815
+ return graphModel.end({ prettyPrint: false });
1816
+ }
1817
+ const mxfile = create({ version: "1.0", encoding: "UTF-8" }).ele("mxfile", {
1818
+ host: resolved.host,
1819
+ modified: resolved.modifiedAt,
1820
+ agent: resolved.agent
1821
+ });
1822
+ mxfile.ele("diagram", {
1823
+ id: document.id,
1824
+ name: "Page-1"
1825
+ }).import(graphModel);
1826
+ return mxfile.end({ prettyPrint: false });
1827
+ };
1828
+
1829
+ import { createHash } from "node:crypto";
1830
+
1831
+ var xmlEscape = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
1832
+ var lineHeight2 = (fontSize) => Math.round(fontSize * 1.2);
1833
+ var splitLongWord2 = (word, maxChars) => {
1834
+ const parts = [];
1835
+ for (let index = 0; index < word.length; index += maxChars) {
1836
+ parts.push(word.slice(index, index + maxChars));
1837
+ }
1838
+ return parts;
1839
+ };
1840
+ var wrapText2 = (text, maxWidth, fontSize, wrap) => {
1841
+ const maxChars = Math.max(1, Math.floor(maxWidth / (fontSize * 0.56)));
1842
+ return text.split("\n").flatMap((paragraph) => {
1843
+ if (!wrap) {
1844
+ return [paragraph];
1845
+ }
1846
+ const words = paragraph.split(/\s+/).filter(Boolean);
1847
+ if (words.length === 0) {
1848
+ return [""];
1849
+ }
1850
+ const lines = [];
1851
+ let current = "";
1852
+ for (const word of words) {
1853
+ const candidates = word.length > maxChars ? splitLongWord2(word, maxChars) : [word];
1854
+ for (const candidate of candidates) {
1855
+ const next = current ? `${current} ${candidate}` : candidate;
1856
+ if (next.length <= maxChars) {
1857
+ current = next;
1858
+ } else {
1859
+ if (current) {
1860
+ lines.push(current);
1861
+ }
1862
+ current = candidate;
1863
+ }
1864
+ }
1865
+ }
1866
+ if (current) {
1867
+ lines.push(current);
1868
+ }
1869
+ return lines.length > 0 ? lines : [""];
1870
+ });
1871
+ };
1872
+ var alignText = (x, width, align) => {
1873
+ if (align === "left") {
1874
+ return { x: x + 12, anchor: "start" };
1875
+ }
1876
+ if (align === "right") {
1877
+ return { x: x + width - 12, anchor: "end" };
1878
+ }
1879
+ return { x: x + Math.round(width / 2), anchor: "middle" };
1880
+ };
1881
+ var computeTextStartY = (y, height, lineCount, fontSize, verticalAlign) => {
1882
+ const totalHeight = lineCount * lineHeight2(fontSize);
1883
+ if (verticalAlign === "top") {
1884
+ return y + fontSize + 8;
1885
+ }
1886
+ if (verticalAlign === "bottom") {
1887
+ return y + height - totalHeight + fontSize - 8;
1888
+ }
1889
+ return y + Math.round((height - totalHeight) / 2) + fontSize;
1890
+ };
1891
+ var renderTextBlock = (node, warnings, isTextOnly = false) => {
1892
+ const lines = wrapText2(node.text, node.width - (isTextOnly ? 0 : 24), node.style.fontSize, node.style.wrap);
1893
+ const maxLines = Math.max(1, Math.floor((node.height - 12) / lineHeight2(node.style.fontSize)));
1894
+ const renderedLines = lines.slice(0, maxLines);
1895
+ if (lines.length > maxLines) {
1896
+ warnings.push(
1897
+ warning("TEXT_OVERFLOW", "Text rendering truncated to fit node bounds", { objectId: node.id })
1898
+ );
1899
+ }
1900
+ const { x, anchor } = alignText(node.x, node.width, node.style.textAlign);
1901
+ const startY = computeTextStartY(
1902
+ node.y,
1903
+ node.height,
1904
+ renderedLines.length,
1905
+ node.style.fontSize,
1906
+ node.style.verticalAlign
1907
+ );
1908
+ const tspans = renderedLines.map(
1909
+ (line, index) => `<tspan x="${x}" dy="${index === 0 ? 0 : lineHeight2(node.style.fontSize)}">${xmlEscape(line)}</tspan>`
1910
+ ).join("");
1911
+ return `<text fill="${node.style.textColor}" font-size="${node.style.fontSize}" font-weight="${node.style.fontWeight}" text-anchor="${anchor}" dominant-baseline="alphabetic">${tspans}</text>`;
1912
+ };
1913
+ var renderNodeShape = (node) => {
1914
+ const common = `fill="${node.type === "text" ? "none" : node.style.fillColor}" stroke="${node.type === "text" ? "none" : node.style.strokeColor}" stroke-width="${node.type === "text" ? 0 : node.style.strokeWidth}"${node.style.dashed ? ` stroke-dasharray="${node.style.strokeWidth * 4} ${node.style.strokeWidth * 2}"` : ""}`;
1915
+ switch (node.type) {
1916
+ case "rectangle":
1917
+ return `<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="0" ry="0" ${common}/>`;
1918
+ case "roundedRectangle":
1919
+ return `<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="12" ry="12" ${common}/>`;
1920
+ case "ellipse":
1921
+ return `<ellipse cx="${node.x + Math.round(node.width / 2)}" cy="${node.y + Math.round(node.height / 2)}" rx="${Math.round(node.width / 2)}" ry="${Math.round(node.height / 2)}" ${common}/>`;
1922
+ case "diamond": {
1923
+ const points = [
1924
+ `${node.x + Math.round(node.width / 2)},${node.y}`,
1925
+ `${node.x + node.width},${node.y + Math.round(node.height / 2)}`,
1926
+ `${node.x + Math.round(node.width / 2)},${node.y + node.height}`,
1927
+ `${node.x},${node.y + Math.round(node.height / 2)}`
1928
+ ].join(" ");
1929
+ return `<polygon points="${points}" ${common}/>`;
1930
+ }
1931
+ case "text":
1932
+ return "";
1933
+ }
1934
+ };
1935
+ var polylineMidpoint = (points) => {
1936
+ if (points.length === 0) {
1937
+ return { x: 0, y: 0 };
1938
+ }
1939
+ const lengths = [];
1940
+ let total = 0;
1941
+ for (let index = 1; index < points.length; index += 1) {
1942
+ const current = points[index];
1943
+ const previous = points[index - 1];
1944
+ const length = Math.hypot(current.x - previous.x, current.y - previous.y);
1945
+ lengths.push(length);
1946
+ total += length;
1947
+ }
1948
+ const target = total / 2;
1949
+ let walked = 0;
1950
+ for (let index = 1; index < points.length; index += 1) {
1951
+ const segmentLength = lengths[index - 1] ?? 0;
1952
+ const current = points[index];
1953
+ const previous = points[index - 1];
1954
+ if (walked + segmentLength >= target) {
1955
+ const ratio = segmentLength === 0 ? 0 : (target - walked) / segmentLength;
1956
+ return {
1957
+ x: Math.round(previous.x + (current.x - previous.x) * ratio),
1958
+ y: Math.round(previous.y + (current.y - previous.y) * ratio)
1959
+ };
1960
+ }
1961
+ walked += segmentLength;
1962
+ }
1963
+ return points.at(-1) ?? points[0];
1964
+ };
1965
+ var renderEdge = (edge, document, warnings) => {
1966
+ const sourceNode = document.nodes.find((node) => node.id === edge.sourceNodeId);
1967
+ const targetNode = document.nodes.find((node) => node.id === edge.targetNodeId);
1968
+ if (!sourceNode || !targetNode) {
1969
+ return "";
1970
+ }
1971
+ const source = getAnchorPoint(sourceNode, edge.route.sourceAnchor);
1972
+ const target = getAnchorPoint(targetNode, edge.route.targetAnchor);
1973
+ const points = [source, ...edge.route.points, target];
1974
+ const stroke = `stroke="${edge.style.strokeColor}" stroke-width="${edge.style.strokeWidth}" fill="none"${edge.style.dashed ? ` stroke-dasharray="${edge.style.strokeWidth * 4} ${edge.style.strokeWidth * 2}"` : ""}`;
1975
+ const markerStart = edge.style.arrowStart !== "none" ? ` marker-start="url(#arrow-${edge.style.arrowStart})"` : "";
1976
+ const markerEnd = edge.style.arrowEnd !== "none" ? ` marker-end="url(#arrow-${edge.style.arrowEnd})"` : "";
1977
+ if (edge.pathType === "orthogonal" && edge.route.points.length === 0) {
1978
+ warnings.push(
1979
+ warning("ORTHOGONAL_ROUTE_MISSING", "Orthogonal edge route has not been calculated", { objectId: edge.id })
1980
+ );
1981
+ }
1982
+ const shape = edge.pathType === "straight" ? `<line x1="${source.x}" y1="${source.y}" x2="${target.x}" y2="${target.y}" ${stroke}${markerStart}${markerEnd}/>` : `<polyline points="${points.map((point) => `${point.x},${point.y}`).join(" ")}" ${stroke}${markerStart}${markerEnd}/>`;
1983
+ const midpoint = polylineMidpoint(points);
1984
+ const label = edge.label ? `<text x="${midpoint.x}" y="${midpoint.y - 6}" fill="${edge.style.textColor}" font-size="${edge.style.fontSize}" font-weight="${edge.style.fontWeight}" text-anchor="middle">${xmlEscape(edge.label)}</text>` : "";
1985
+ return `<g data-object-id="${edge.id}" data-object-type="edge" data-z-index="0">${shape}${label}</g>`;
1986
+ };
1987
+ var renderNode = (node, warnings) => {
1988
+ const shape = renderNodeShape(node);
1989
+ const text = node.text ? renderTextBlock(node, warnings, node.type === "text") : "";
1990
+ return `<g data-object-id="${node.id}" data-object-type="${node.type}" data-z-index="${node.zIndex}">${shape}${text}</g>`;
1991
+ };
1992
+ var markers = () => `
1993
+ <defs>
1994
+ <marker id="arrow-classic" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
1995
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="context-stroke"/>
1996
+ </marker>
1997
+ <marker id="arrow-block" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
1998
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="context-stroke"/>
1999
+ </marker>
2000
+ <marker id="arrow-open" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
2001
+ <path d="M 1 1 L 9 5 L 1 9" fill="none" stroke="context-stroke" stroke-width="1.5"/>
2002
+ </marker>
2003
+ </defs>
2004
+ `;
2005
+ var renderDocumentAsSvg = (document) => {
2006
+ const warnings = [];
2007
+ const viewBox = `0 0 ${document.canvas.width} ${document.canvas.height}`;
2008
+ const nodeMarkup = document.nodes.map((node) => renderNode(node, warnings)).join("");
2009
+ const edgeMarkup = document.edges.map((edge) => renderEdge(edge, document, warnings)).join("");
2010
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}" width="${document.canvas.width}" height="${document.canvas.height}">${markers()}<rect x="0" y="0" width="${document.canvas.width}" height="${document.canvas.height}" fill="${document.canvas.backgroundColor}"/>${edgeMarkup}${nodeMarkup}</svg>`;
2011
+ return {
2012
+ svg,
2013
+ viewBox,
2014
+ warnings
2015
+ };
2016
+ };
2017
+ var renderSvgDocument = renderDocumentAsSvg;
2018
+
2019
+ import { Resvg } from "@resvg/resvg-js";
2020
+ var PNG_EXPORT_SCALES = [1, 2, 3];
2021
+ var exportDocumentToPng = (document, scale = 1) => {
2022
+ if (!PNG_EXPORT_SCALES.includes(scale)) {
2023
+ throw new RangeError(`PNG export scale must be one of ${PNG_EXPORT_SCALES.join(", ")}`);
2024
+ }
2025
+ const rendered = renderSvgDocument(document);
2026
+ const resvg = new Resvg(rendered.svg, {
2027
+ background: document.canvas.backgroundColor,
2028
+ fitTo: {
2029
+ mode: "zoom",
2030
+ value: scale
2031
+ }
2032
+ });
2033
+ const png = resvg.render();
2034
+ const content = png.asPng();
2035
+ return {
2036
+ content,
2037
+ width: document.canvas.width * scale,
2038
+ height: document.canvas.height * scale,
2039
+ scale,
2040
+ svg: rendered.svg,
2041
+ warnings: rendered.warnings,
2042
+ sha256: createHash("sha256").update(content).digest("hex")
2043
+ };
2044
+ };
2045
+
2046
+ var dedupeIssues = (items) => {
2047
+ const seen = /* @__PURE__ */ new Set();
2048
+ return items.filter((item) => {
2049
+ const key = JSON.stringify(item);
2050
+ if (seen.has(key)) {
2051
+ return false;
2052
+ }
2053
+ seen.add(key);
2054
+ return true;
2055
+ });
2056
+ };
2057
+ var mergeWarnings = (...groups) => {
2058
+ const seen = /* @__PURE__ */ new Set();
2059
+ return groups.flat().filter((item) => {
2060
+ const key = `${item.code}:${item.objectId ?? ""}:${item.fieldPath ?? ""}:${item.severity}`;
2061
+ if (seen.has(key)) {
2062
+ return false;
2063
+ }
2064
+ seen.add(key);
2065
+ return true;
2066
+ });
2067
+ };
2068
+ var validateNormalizedDocument = (document) => {
2069
+ const parsed = safeParseWithErrors(documentSchema, document);
2070
+ const semantic = validateDocumentSemantics(document);
2071
+ return {
2072
+ errors: dedupeIssues(parsed.success ? semantic.errors : [...parsed.errors, ...semantic.errors]),
2073
+ warnings: dedupeIssues(semantic.warnings)
2074
+ };
2075
+ };
2076
+ var applyActorAccess = (document, actor) => {
2077
+ if (document.metadata.tenantId !== actor.tenantId || document.metadata.ownerId !== actor.ownerId) {
2078
+ return [error("FORBIDDEN", "Document belongs to a different owner or tenant")];
2079
+ }
2080
+ return [];
2081
+ };
2082
+ var cloneDocumentWithFreshIds = (document, actor, title) => {
2083
+ const nodeIdMap = new Map(document.nodes.map((node) => [node.id, generateNodeId()]));
2084
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2085
+ return normalizeDocument({
2086
+ ...document,
2087
+ id: generateDocumentId(),
2088
+ title: title ?? `${document.title} Copy`,
2089
+ revision: 1,
2090
+ metadata: {
2091
+ ...document.metadata,
2092
+ createdBy: actor.actorId,
2093
+ createdAt: timestamp,
2094
+ updatedAt: timestamp,
2095
+ ownerId: actor.ownerId,
2096
+ tenantId: actor.tenantId
2097
+ },
2098
+ nodes: document.nodes.map((node) => ({
2099
+ ...node,
2100
+ id: nodeIdMap.get(node.id) ?? generateNodeId()
2101
+ })),
2102
+ edges: document.edges.map((edge) => ({
2103
+ ...edge,
2104
+ id: generateEdgeId(),
2105
+ sourceNodeId: nodeIdMap.get(edge.sourceNodeId) ?? edge.sourceNodeId,
2106
+ targetNodeId: nodeIdMap.get(edge.targetNodeId) ?? edge.targetNodeId
2107
+ }))
2108
+ });
2109
+ };
2110
+ var DocumentService = class {
2111
+ constructor(repository) {
2112
+ this.repository = repository;
2113
+ }
2114
+ repository;
2115
+ logOperation(params) {
2116
+ this.repository.logOperation({
2117
+ requestId: params.requestId,
2118
+ documentId: params.documentId,
2119
+ tenantId: params.actor.tenantId,
2120
+ actorId: params.actor.actorId,
2121
+ operationType: params.operationType,
2122
+ success: params.success,
2123
+ revision: params.revision,
2124
+ changedObjectIds: params.changedObjectIds,
2125
+ errors: params.errors,
2126
+ warnings: params.warnings
2127
+ });
2128
+ }
2129
+ success(requestId, document, changedObjectIds, warnings = [], data) {
2130
+ return {
2131
+ requestId,
2132
+ success: true,
2133
+ revision: document.revision,
2134
+ errors: [],
2135
+ warnings,
2136
+ changedObjectIds,
2137
+ normalizedDocument: document,
2138
+ ...data !== void 0 ? { data } : {}
2139
+ };
2140
+ }
2141
+ failure(requestId, errors, warnings = [], document) {
2142
+ return {
2143
+ requestId,
2144
+ success: false,
2145
+ revision: document?.revision ?? null,
2146
+ errors,
2147
+ warnings,
2148
+ changedObjectIds: [],
2149
+ ...document ? { normalizedDocument: document } : {}
2150
+ };
2151
+ }
2152
+ loadDocument(documentId, actor) {
2153
+ const document = this.repository.getDocument(documentId);
2154
+ if (!document) {
2155
+ return { errors: [error("DOCUMENT_NOT_FOUND", "Document not found", { objectId: documentId })] };
2156
+ }
2157
+ const accessErrors = applyActorAccess(document, actor);
2158
+ if (accessErrors.length > 0) {
2159
+ return { errors: accessErrors };
2160
+ }
2161
+ return { document, errors: [] };
2162
+ }
2163
+ ensureRevision(document, baseRevision) {
2164
+ return document.revision === baseRevision ? [] : [
2165
+ error("REVISION_CONFLICT", "Document revision does not match baseRevision", {
2166
+ objectId: document.id
2167
+ })
2168
+ ];
2169
+ }
2170
+ persistDocument(requestId, actor, operationType, document, changedObjectIds, warnings, isCreate = false) {
2171
+ this.repository.transaction(() => {
2172
+ if (isCreate) {
2173
+ this.repository.insertDocument(document);
2174
+ } else {
2175
+ this.repository.updateDocument(document);
2176
+ }
2177
+ this.logOperation({
2178
+ requestId,
2179
+ actor,
2180
+ operationType,
2181
+ documentId: document.id,
2182
+ success: true,
2183
+ revision: document.revision,
2184
+ changedObjectIds,
2185
+ errors: [],
2186
+ warnings
2187
+ });
2188
+ });
2189
+ return this.success(requestId, document, changedObjectIds, warnings);
2190
+ }
2191
+ createDocument(requestId, actor, input) {
2192
+ const document = createDocument(input, actor);
2193
+ const validation = validateNormalizedDocument(document);
2194
+ if (validation.errors.length > 0) {
2195
+ return this.failure(requestId, validation.errors, validation.warnings, document);
2196
+ }
2197
+ try {
2198
+ return this.persistDocument(requestId, actor, "createDocument", document, [document.id], validation.warnings, true);
2199
+ } catch (cause) {
2200
+ if (cause instanceof DocumentAlreadyExistsError) {
2201
+ const errors = [error("DOCUMENT_ALREADY_EXISTS", "Document already exists", { objectId: document.id })];
2202
+ this.logOperation({
2203
+ requestId,
2204
+ actor,
2205
+ operationType: "createDocument",
2206
+ documentId: document.id,
2207
+ success: false,
2208
+ revision: null,
2209
+ changedObjectIds: [],
2210
+ errors,
2211
+ warnings: []
2212
+ });
2213
+ return this.failure(requestId, errors);
2214
+ }
2215
+ throw cause;
2216
+ }
2217
+ }
2218
+ getDocument(requestId, actor, documentId) {
2219
+ const loaded = this.loadDocument(documentId, actor);
2220
+ if (loaded.errors.length > 0 || !loaded.document) {
2221
+ return this.failure(requestId, loaded.errors);
2222
+ }
2223
+ return this.success(requestId, loaded.document, []);
2224
+ }
2225
+ cloneDocument(requestId, actor, documentId, title) {
2226
+ const loaded = this.loadDocument(documentId, actor);
2227
+ if (loaded.errors.length > 0 || !loaded.document) {
2228
+ return this.failure(requestId, loaded.errors);
2229
+ }
2230
+ const cloned = cloneDocumentWithFreshIds(loaded.document, actor, title);
2231
+ return this.persistDocument(requestId, actor, "cloneDocument", cloned, [cloned.id], [], true);
2232
+ }
2233
+ updateWithMutation(requestId, actor, documentId, baseRevision, operationType, mutate) {
2234
+ const loaded = this.loadDocument(documentId, actor);
2235
+ if (loaded.errors.length > 0 || !loaded.document) {
2236
+ return this.failure(requestId, loaded.errors);
2237
+ }
2238
+ const revisionErrors = this.ensureRevision(loaded.document, baseRevision);
2239
+ if (revisionErrors.length > 0) {
2240
+ return this.failure(requestId, revisionErrors, [], loaded.document);
2241
+ }
2242
+ const result = mutate(loaded.document);
2243
+ if (result.errors && result.errors.length > 0) {
2244
+ return this.failure(requestId, result.errors, [], loaded.document);
2245
+ }
2246
+ const nextDocument = withRevision(result.document, loaded.document.revision + 1);
2247
+ const validation = validateNormalizedDocument(nextDocument);
2248
+ if (validation.errors.length > 0) {
2249
+ this.logOperation({
2250
+ requestId,
2251
+ actor,
2252
+ operationType,
2253
+ documentId,
2254
+ success: false,
2255
+ revision: loaded.document.revision,
2256
+ changedObjectIds: result.changedObjectIds,
2257
+ errors: validation.errors,
2258
+ warnings: validation.warnings
2259
+ });
2260
+ return this.failure(requestId, validation.errors, validation.warnings, loaded.document);
2261
+ }
2262
+ return this.persistDocument(
2263
+ requestId,
2264
+ actor,
2265
+ operationType,
2266
+ nextDocument,
2267
+ result.changedObjectIds,
2268
+ validation.warnings
2269
+ );
2270
+ }
2271
+ addNode(requestId, actor, documentId, baseRevision, node) {
2272
+ return this.updateWithMutation(
2273
+ requestId,
2274
+ actor,
2275
+ documentId,
2276
+ baseRevision,
2277
+ "addNode",
2278
+ (document) => addNode(document, node)
2279
+ );
2280
+ }
2281
+ updateNode(requestId, actor, documentId, baseRevision, nodeId, changes) {
2282
+ return this.updateWithMutation(requestId, actor, documentId, baseRevision, "updateNode", (document) => {
2283
+ if (!document.nodes.some((node) => node.id === nodeId)) {
2284
+ return {
2285
+ document,
2286
+ changedObjectIds: [],
2287
+ errors: [error("NODE_NOT_FOUND", "Node not found", { objectId: nodeId })]
2288
+ };
2289
+ }
2290
+ return updateNode(document, nodeId, changes);
2291
+ });
2292
+ }
2293
+ deleteNode(requestId, actor, documentId, baseRevision, nodeId) {
2294
+ return this.updateWithMutation(requestId, actor, documentId, baseRevision, "deleteNode", (document) => {
2295
+ if (!document.nodes.some((node) => node.id === nodeId)) {
2296
+ return {
2297
+ document,
2298
+ changedObjectIds: [],
2299
+ errors: [error("NODE_NOT_FOUND", "Node not found", { objectId: nodeId })]
2300
+ };
2301
+ }
2302
+ return deleteNode(document, nodeId);
2303
+ });
2304
+ }
2305
+ addEdge(requestId, actor, documentId, baseRevision, edge) {
2306
+ return this.updateWithMutation(
2307
+ requestId,
2308
+ actor,
2309
+ documentId,
2310
+ baseRevision,
2311
+ "addEdge",
2312
+ (document) => addEdge(document, edge)
2313
+ );
2314
+ }
2315
+ updateEdge(requestId, actor, documentId, baseRevision, edgeId, changes) {
2316
+ return this.updateWithMutation(requestId, actor, documentId, baseRevision, "updateEdge", (document) => {
2317
+ if (!document.edges.some((edge) => edge.id === edgeId)) {
2318
+ return {
2319
+ document,
2320
+ changedObjectIds: [],
2321
+ errors: [error("EDGE_NOT_FOUND", "Edge not found", { objectId: edgeId })]
2322
+ };
2323
+ }
2324
+ return updateEdge(document, edgeId, changes);
2325
+ });
2326
+ }
2327
+ deleteEdge(requestId, actor, documentId, baseRevision, edgeId) {
2328
+ return this.updateWithMutation(requestId, actor, documentId, baseRevision, "deleteEdge", (document) => {
2329
+ if (!document.edges.some((edge) => edge.id === edgeId)) {
2330
+ return {
2331
+ document,
2332
+ changedObjectIds: [],
2333
+ errors: [error("EDGE_NOT_FOUND", "Edge not found", { objectId: edgeId })]
2334
+ };
2335
+ }
2336
+ return deleteEdge(document, edgeId);
2337
+ });
2338
+ }
2339
+ patchDocument(requestId, actor, documentId, baseRevision, operations, atomic = true, dryRun = false) {
2340
+ const loaded = this.loadDocument(documentId, actor);
2341
+ if (loaded.errors.length > 0 || !loaded.document) {
2342
+ return this.failure(requestId, loaded.errors);
2343
+ }
2344
+ const revisionErrors = this.ensureRevision(loaded.document, baseRevision);
2345
+ if (revisionErrors.length > 0) {
2346
+ return this.failure(requestId, revisionErrors, [], loaded.document);
2347
+ }
2348
+ const preview = applyPatchOperations(loaded.document, operations, loaded.document.revision + 1);
2349
+ const previewDocument = withRevision(preview.document, loaded.document.revision + 1);
2350
+ const previewValidation = validateNormalizedDocument(previewDocument);
2351
+ if (atomic && previewValidation.errors.length > 0) {
2352
+ this.logOperation({
2353
+ requestId,
2354
+ actor,
2355
+ operationType: "patchDocument",
2356
+ documentId,
2357
+ success: false,
2358
+ revision: loaded.document.revision,
2359
+ changedObjectIds: preview.changedObjectIds,
2360
+ errors: previewValidation.errors,
2361
+ warnings: previewValidation.warnings
2362
+ });
2363
+ return this.failure(requestId, previewValidation.errors, previewValidation.warnings, loaded.document);
2364
+ }
2365
+ if (!atomic) {
2366
+ let current = loaded.document;
2367
+ const changedObjectIds = /* @__PURE__ */ new Set();
2368
+ const warnings = [];
2369
+ const errors = [];
2370
+ for (const operation of operations) {
2371
+ const candidate = applyPatchOperations(current, [operation], loaded.document.revision + 1);
2372
+ const candidateDocument = withRevision(candidate.document, loaded.document.revision + 1);
2373
+ const candidateValidation = validateNormalizedDocument(candidateDocument);
2374
+ if (candidateValidation.errors.length > 0) {
2375
+ errors.push(...candidateValidation.errors);
2376
+ warnings.push(...candidateValidation.warnings);
2377
+ continue;
2378
+ }
2379
+ current = candidateDocument;
2380
+ for (const objectId of candidate.changedObjectIds) {
2381
+ changedObjectIds.add(objectId);
2382
+ }
2383
+ warnings.push(...candidateValidation.warnings);
2384
+ }
2385
+ if (errors.length > 0 && changedObjectIds.size === 0) {
2386
+ return this.failure(requestId, dedupeIssues(errors), dedupeIssues(warnings), loaded.document);
2387
+ }
2388
+ if (dryRun) {
2389
+ return this.success(requestId, current, [...changedObjectIds], dedupeIssues(warnings));
2390
+ }
2391
+ return this.persistDocument(
2392
+ requestId,
2393
+ actor,
2394
+ "patchDocument",
2395
+ current,
2396
+ [...changedObjectIds],
2397
+ dedupeIssues(warnings)
2398
+ );
2399
+ }
2400
+ if (dryRun) {
2401
+ return this.success(
2402
+ requestId,
2403
+ previewDocument,
2404
+ preview.changedObjectIds,
2405
+ previewValidation.warnings
2406
+ );
2407
+ }
2408
+ return this.persistDocument(
2409
+ requestId,
2410
+ actor,
2411
+ "patchDocument",
2412
+ previewDocument,
2413
+ preview.changedObjectIds,
2414
+ previewValidation.warnings
2415
+ );
2416
+ }
2417
+ validateDocument(requestId, actor, documentId, target, drawioMode) {
2418
+ const loaded = this.loadDocument(documentId, actor);
2419
+ if (loaded.errors.length > 0 || !loaded.document) {
2420
+ return this.failure(requestId, loaded.errors);
2421
+ }
2422
+ if (target === "document") {
2423
+ const validation2 = validateNormalizedDocument(loaded.document);
2424
+ return {
2425
+ ...this.success(requestId, loaded.document, [], validation2.warnings, {
2426
+ target,
2427
+ valid: validation2.errors.length === 0
2428
+ }),
2429
+ success: validation2.errors.length === 0,
2430
+ errors: validation2.errors
2431
+ };
2432
+ }
2433
+ const xml = exportDrawioDocument(loaded.document, { mode: drawioMode });
2434
+ const validation = validateDrawioXml(xml);
2435
+ return {
2436
+ ...this.success(requestId, loaded.document, [], validation.warnings, {
2437
+ target,
2438
+ valid: validation.errors.length === 0
2439
+ }),
2440
+ success: validation.errors.length === 0,
2441
+ errors: validation.errors
2442
+ };
2443
+ }
2444
+ renderDocument(requestId, actor, documentId) {
2445
+ const loaded = this.loadDocument(documentId, actor);
2446
+ if (loaded.errors.length > 0 || !loaded.document) {
2447
+ return this.failure(requestId, loaded.errors);
2448
+ }
2449
+ const rendered = renderSvgDocument(loaded.document);
2450
+ const validation = validateNormalizedDocument(loaded.document);
2451
+ return this.success(requestId, loaded.document, [], mergeWarnings(validation.warnings, rendered.warnings), {
2452
+ svg: rendered.svg,
2453
+ viewBox: rendered.viewBox
2454
+ });
2455
+ }
2456
+ exportDocument(requestId, actor, documentId, format, drawioMode, scale = 1) {
2457
+ const loaded = this.loadDocument(documentId, actor);
2458
+ if (loaded.errors.length > 0 || !loaded.document) {
2459
+ return this.failure(requestId, loaded.errors);
2460
+ }
2461
+ if (format === "svg") {
2462
+ const rendered = renderSvgDocument(loaded.document);
2463
+ const validation2 = validateNormalizedDocument(loaded.document);
2464
+ return this.success(requestId, loaded.document, [], mergeWarnings(validation2.warnings, rendered.warnings), {
2465
+ format,
2466
+ contentType: "image/svg+xml",
2467
+ content: rendered.svg
2468
+ });
2469
+ }
2470
+ if (format === "png") {
2471
+ const rendered = exportDocumentToPng(loaded.document, scale);
2472
+ const validation2 = validateNormalizedDocument(loaded.document);
2473
+ return this.success(requestId, loaded.document, [], mergeWarnings(validation2.warnings, rendered.warnings), {
2474
+ format,
2475
+ contentType: "image/png",
2476
+ content: Buffer.from(rendered.content).toString("base64"),
2477
+ encoding: "base64"
2478
+ });
2479
+ }
2480
+ const xml = exportDrawioDocument(loaded.document, { mode: drawioMode });
2481
+ const validation = validateDrawioXml(xml);
2482
+ if (validation.errors.length > 0) {
2483
+ return this.failure(requestId, validation.errors, validation.warnings, loaded.document);
2484
+ }
2485
+ return this.success(requestId, loaded.document, [], validation.warnings, {
2486
+ format,
2487
+ contentType: "application/xml",
2488
+ content: xml
2489
+ });
2490
+ }
2491
+ };
2492
+
2493
+ var createApp = async (options = {}) => {
2494
+ const app = Fastify({ logger: false });
2495
+ const repository = new SqliteDocumentRepository(options.dbPath ?? process.env.DROWAI_DB_PATH ?? ":memory:");
2496
+ const documentService = new DocumentService(repository);
2497
+ const auth = resolveAuthOptions(options.auth);
2498
+ registerDocumentRoutes(app, { documentService, auth });
2499
+ app.addHook("onClose", async () => {
2500
+ repository.close();
2501
+ });
2502
+ return app;
2503
+ };
2504
+
2505
+ import { z as z2 } from "zod";
2506
+ var SUPPORTED_PROTOCOL_VERSIONS = [
2507
+ "2025-11-25",
2508
+ "2025-06-18",
2509
+ "2025-03-26",
2510
+ "2024-11-05"
2511
+ ];
2512
+ var DROWAI_MCP_SERVER_NAME = "drowai-orchestrator";
2513
+ var DROWAI_MCP_SERVER_TITLE = "DrowAI Orchestrator";
2514
+ var DROWAI_MCP_SERVER_VERSION = "0.1.0";
2515
+ var SERVER_INSTRUCTIONS = "Use these tools as a thin adapter over DrowAI's canonical HTTP JSON API. Mutations require baseRevision. The normalizedDocument in each result remains the source of truth.";
2516
+ var MCP_LOCAL_ACTOR = {
2517
+ actorId: "mcp-local",
2518
+ ownerId: "mcp-local",
2519
+ tenantId: "local"
2520
+ };
2521
+ var documentIdToolSchema = z2.object({
2522
+ documentId: z2.string().min(1).describe("Target document id, for example doc_examplecase")
2523
+ });
2524
+ var nodeIdToolSchema = z2.object({
2525
+ nodeId: z2.string().min(1).describe("Target node id")
2526
+ });
2527
+ var edgeIdToolSchema = z2.object({
2528
+ edgeId: z2.string().min(1).describe("Target edge id")
2529
+ });
2530
+ var getDocumentToolSchema = documentIdToolSchema.strict();
2531
+ var addNodeToolSchema = addNodeRequestSchema.extend(documentIdToolSchema.shape).strict();
2532
+ var updateNodeToolSchema = updateNodeRequestSchema.extend({
2533
+ ...documentIdToolSchema.shape,
2534
+ ...nodeIdToolSchema.shape
2535
+ }).strict();
2536
+ var deleteNodeToolSchema = baseRevisionSchema.extend({
2537
+ ...documentIdToolSchema.shape,
2538
+ ...nodeIdToolSchema.shape
2539
+ }).strict();
2540
+ var addEdgeToolSchema = addEdgeRequestSchema.extend(documentIdToolSchema.shape).strict();
2541
+ var updateEdgeToolSchema = updateEdgeRequestSchema.extend({
2542
+ ...documentIdToolSchema.shape,
2543
+ ...edgeIdToolSchema.shape
2544
+ }).strict();
2545
+ var deleteEdgeToolSchema = baseRevisionSchema.extend({
2546
+ ...documentIdToolSchema.shape,
2547
+ ...edgeIdToolSchema.shape
2548
+ }).strict();
2549
+ var patchDocumentToolSchema = patchRequestSchema.extend(documentIdToolSchema.shape).strict();
2550
+ var validateDocumentToolSchema = validateRequestSchema.extend(documentIdToolSchema.shape).strict();
2551
+ var renderDocumentToolSchema = renderRequestSchema.extend(documentIdToolSchema.shape).strict();
2552
+ var exportDocumentToolSchema = exportRequestSchema.extend(documentIdToolSchema.shape).strict();
2553
+ var ApiBridge = class {
2554
+ constructor(request) {
2555
+ this.request = request;
2556
+ }
2557
+ request;
2558
+ encode(segment) {
2559
+ return encodeURIComponent(segment);
2560
+ }
2561
+ createDocument(payload) {
2562
+ return this.request("POST", "/documents", payload);
2563
+ }
2564
+ getDocument(documentId) {
2565
+ return this.request("GET", `/documents/${this.encode(documentId)}`);
2566
+ }
2567
+ addNode(documentId, payload) {
2568
+ return this.request("POST", `/documents/${this.encode(documentId)}/nodes`, payload);
2569
+ }
2570
+ updateNode(documentId, nodeId, payload) {
2571
+ return this.request("PATCH", `/documents/${this.encode(documentId)}/nodes/${this.encode(nodeId)}`, payload);
2572
+ }
2573
+ deleteNode(documentId, nodeId, payload) {
2574
+ return this.request("DELETE", `/documents/${this.encode(documentId)}/nodes/${this.encode(nodeId)}`, payload);
2575
+ }
2576
+ addEdge(documentId, payload) {
2577
+ return this.request("POST", `/documents/${this.encode(documentId)}/edges`, payload);
2578
+ }
2579
+ updateEdge(documentId, edgeId, payload) {
2580
+ return this.request("PATCH", `/documents/${this.encode(documentId)}/edges/${this.encode(edgeId)}`, payload);
2581
+ }
2582
+ deleteEdge(documentId, edgeId, payload) {
2583
+ return this.request("DELETE", `/documents/${this.encode(documentId)}/edges/${this.encode(edgeId)}`, payload);
2584
+ }
2585
+ patchDocument(documentId, payload) {
2586
+ return this.request("POST", `/documents/${this.encode(documentId)}/patch`, payload);
2587
+ }
2588
+ validateDocument(documentId, payload) {
2589
+ return this.request("POST", `/documents/${this.encode(documentId)}/validate`, payload);
2590
+ }
2591
+ renderDocument(documentId, payload) {
2592
+ return this.request("POST", `/documents/${this.encode(documentId)}/render`, payload);
2593
+ }
2594
+ exportDocument(documentId, payload) {
2595
+ return this.request("POST", `/documents/${this.encode(documentId)}/export`, payload);
2596
+ }
2597
+ };
2598
+ var createToolContracts = () => [
2599
+ {
2600
+ name: "create_document",
2601
+ title: "Create document",
2602
+ description: "Create a new DrowAI document and return the normalized JSON envelope.",
2603
+ schema: createDocumentRequestSchema,
2604
+ execute: (args, api) => api.createDocument(args)
2605
+ },
2606
+ {
2607
+ name: "get_document",
2608
+ title: "Get document",
2609
+ description: "Fetch a normalized DrowAI document by id.",
2610
+ schema: getDocumentToolSchema,
2611
+ execute: (args, api) => api.getDocument(args.documentId)
2612
+ },
2613
+ {
2614
+ name: "add_node",
2615
+ title: "Add node",
2616
+ description: "Append one node to an existing document.",
2617
+ schema: addNodeToolSchema,
2618
+ execute: (args, api) => {
2619
+ const { documentId, ...payload } = args;
2620
+ return api.addNode(documentId, payload);
2621
+ }
2622
+ },
2623
+ {
2624
+ name: "update_node",
2625
+ title: "Update node",
2626
+ description: "Apply a partial update to one node.",
2627
+ schema: updateNodeToolSchema,
2628
+ execute: (args, api) => {
2629
+ const { documentId, nodeId, ...payload } = args;
2630
+ return api.updateNode(documentId, nodeId, payload);
2631
+ }
2632
+ },
2633
+ {
2634
+ name: "delete_node",
2635
+ title: "Delete node",
2636
+ description: "Delete one node and any connected edges.",
2637
+ schema: deleteNodeToolSchema,
2638
+ execute: (args, api) => {
2639
+ const { documentId, nodeId, ...payload } = args;
2640
+ return api.deleteNode(documentId, nodeId, payload);
2641
+ }
2642
+ },
2643
+ {
2644
+ name: "add_edge",
2645
+ title: "Add edge",
2646
+ description: "Append one edge to an existing document.",
2647
+ schema: addEdgeToolSchema,
2648
+ execute: (args, api) => {
2649
+ const { documentId, ...payload } = args;
2650
+ return api.addEdge(documentId, payload);
2651
+ }
2652
+ },
2653
+ {
2654
+ name: "update_edge",
2655
+ title: "Update edge",
2656
+ description: "Apply a partial update to one edge.",
2657
+ schema: updateEdgeToolSchema,
2658
+ execute: (args, api) => {
2659
+ const { documentId, edgeId, ...payload } = args;
2660
+ return api.updateEdge(documentId, edgeId, payload);
2661
+ }
2662
+ },
2663
+ {
2664
+ name: "delete_edge",
2665
+ title: "Delete edge",
2666
+ description: "Delete one edge from a document.",
2667
+ schema: deleteEdgeToolSchema,
2668
+ execute: (args, api) => {
2669
+ const { documentId, edgeId, ...payload } = args;
2670
+ return api.deleteEdge(documentId, edgeId, payload);
2671
+ }
2672
+ },
2673
+ {
2674
+ name: "patch_document",
2675
+ title: "Patch document",
2676
+ description: "Run ordered patch operations, including orthogonal route recalculation.",
2677
+ schema: patchDocumentToolSchema,
2678
+ execute: (args, api) => {
2679
+ const { documentId, ...payload } = args;
2680
+ return api.patchDocument(documentId, payload);
2681
+ }
2682
+ },
2683
+ {
2684
+ name: "validate_document",
2685
+ title: "Validate document",
2686
+ description: "Validate a normalized document or its draw.io export.",
2687
+ schema: validateDocumentToolSchema,
2688
+ execute: (args, api) => {
2689
+ const { documentId, ...payload } = args;
2690
+ return api.validateDocument(documentId, payload);
2691
+ }
2692
+ },
2693
+ {
2694
+ name: "render_document",
2695
+ title: "Render document",
2696
+ description: "Render the canonical document JSON into SVG.",
2697
+ schema: renderDocumentToolSchema,
2698
+ execute: (args, api) => {
2699
+ const { documentId, ...payload } = args;
2700
+ return api.renderDocument(documentId, payload);
2701
+ }
2702
+ },
2703
+ {
2704
+ name: "export_document",
2705
+ title: "Export document",
2706
+ description: "Export the canonical document JSON to draw.io XML, SVG, or PNG.",
2707
+ schema: exportDocumentToolSchema,
2708
+ execute: (args, api) => {
2709
+ const { documentId, ...payload } = args;
2710
+ return api.exportDocument(documentId, payload);
2711
+ }
2712
+ }
2713
+ ];
2714
+ var jsonRpcSuccess = (id, result) => ({
2715
+ jsonrpc: "2.0",
2716
+ id,
2717
+ result
2718
+ });
2719
+ var jsonRpcError = (id, code, message, data) => ({
2720
+ jsonrpc: "2.0",
2721
+ id,
2722
+ error: {
2723
+ code,
2724
+ message,
2725
+ ...data !== void 0 ? { data } : {}
2726
+ }
2727
+ });
2728
+ var toolInputSchema = (schema) => z2.toJSONSchema(schema);
2729
+ var toolResult = (payload, isError = false) => ({
2730
+ content: [
2731
+ {
2732
+ type: "text",
2733
+ text: JSON.stringify(payload, null, 2)
2734
+ }
2735
+ ],
2736
+ structuredContent: payload,
2737
+ isError
2738
+ });
2739
+ var toolArgumentError = (errors) => toolResult(
2740
+ {
2741
+ success: false,
2742
+ revision: null,
2743
+ errors,
2744
+ warnings: [],
2745
+ changedObjectIds: []
2746
+ },
2747
+ true
2748
+ );
2749
+ var selectProtocolVersion = (requested) => requested && SUPPORTED_PROTOCOL_VERSIONS.includes(requested) ? requested : SUPPORTED_PROTOCOL_VERSIONS[0];
2750
+ var DrowaiMcpServer = class _DrowaiMcpServer {
2751
+ constructor(app, authToken) {
2752
+ this.app = app;
2753
+ this.bridge = new ApiBridge(async (method, path, payload) => {
2754
+ const injectOptions = {
2755
+ method,
2756
+ url: path,
2757
+ headers: {
2758
+ authorization: `Bearer ${authToken}`
2759
+ },
2760
+ ...payload !== void 0 ? { payload } : {}
2761
+ };
2762
+ const response = await this.app.inject(injectOptions);
2763
+ return JSON.parse(String(response.body));
2764
+ });
2765
+ }
2766
+ app;
2767
+ tools = createToolContracts();
2768
+ toolMap = new Map(this.tools.map((tool) => [tool.name, tool]));
2769
+ bridge;
2770
+ negotiatedProtocolVersion = null;
2771
+ static async create(options = {}) {
2772
+ const authToken = options.authToken ?? randomUUID();
2773
+ const app = await createApp({
2774
+ ...options.dbPath ? { dbPath: options.dbPath } : {},
2775
+ auth: {
2776
+ tokenRegistry: {
2777
+ [authToken]: MCP_LOCAL_ACTOR
2778
+ }
2779
+ }
2780
+ });
2781
+ return new _DrowaiMcpServer(app, authToken);
2782
+ }
2783
+ async close() {
2784
+ await this.app.close();
2785
+ }
2786
+ async handleMessage(message) {
2787
+ if (Array.isArray(message)) {
2788
+ const responses = [];
2789
+ for (const item of message) {
2790
+ const response = await this.handleRequest(item);
2791
+ if (response) {
2792
+ responses.push(response);
2793
+ }
2794
+ }
2795
+ return responses.length > 0 ? responses : void 0;
2796
+ }
2797
+ return this.handleRequest(message);
2798
+ }
2799
+ async handleRequest(message) {
2800
+ const id = "id" in message ? message.id ?? null : null;
2801
+ if (message.jsonrpc !== "2.0") {
2802
+ return jsonRpcError(id, -32600, "Invalid JSON-RPC version");
2803
+ }
2804
+ if (typeof message.method !== "string") {
2805
+ return "id" in message ? jsonRpcError(id, -32600, "Invalid JSON-RPC request") : void 0;
2806
+ }
2807
+ switch (message.method) {
2808
+ case "initialize":
2809
+ return this.initialize(id, message.params);
2810
+ case "notifications/initialized":
2811
+ return void 0;
2812
+ case "ping":
2813
+ return jsonRpcSuccess(id, {});
2814
+ case "tools/list":
2815
+ return this.listTools(id);
2816
+ case "tools/call":
2817
+ return this.callTool(id, message.params);
2818
+ default:
2819
+ return "id" in message ? jsonRpcError(id, -32601, `Method not found: ${message.method}`) : void 0;
2820
+ }
2821
+ }
2822
+ initialize(id, params) {
2823
+ const parsed = safeParseWithErrors(
2824
+ z2.object({
2825
+ protocolVersion: z2.string().min(1),
2826
+ capabilities: z2.record(z2.string(), z2.unknown()).default({}),
2827
+ clientInfo: z2.object({
2828
+ name: z2.string().min(1),
2829
+ version: z2.string().min(1),
2830
+ title: z2.string().optional()
2831
+ }).strict()
2832
+ }).strict(),
2833
+ params
2834
+ );
2835
+ if (!parsed.success) {
2836
+ return jsonRpcError(id, -32602, "Invalid initialize params", { errors: parsed.errors });
2837
+ }
2838
+ const selectedProtocolVersion = selectProtocolVersion(parsed.data.protocolVersion);
2839
+ this.negotiatedProtocolVersion = selectedProtocolVersion;
2840
+ return jsonRpcSuccess(id, {
2841
+ protocolVersion: selectedProtocolVersion,
2842
+ capabilities: {
2843
+ tools: {
2844
+ listChanged: false
2845
+ }
2846
+ },
2847
+ serverInfo: {
2848
+ name: DROWAI_MCP_SERVER_NAME,
2849
+ title: DROWAI_MCP_SERVER_TITLE,
2850
+ version: DROWAI_MCP_SERVER_VERSION
2851
+ },
2852
+ instructions: SERVER_INSTRUCTIONS
2853
+ });
2854
+ }
2855
+ listTools(id) {
2856
+ if (!this.negotiatedProtocolVersion) {
2857
+ return jsonRpcError(id, -32002, "Server not initialized");
2858
+ }
2859
+ return jsonRpcSuccess(id, {
2860
+ tools: this.tools.map((tool) => ({
2861
+ name: tool.name,
2862
+ title: tool.title,
2863
+ description: tool.description,
2864
+ inputSchema: toolInputSchema(tool.schema)
2865
+ }))
2866
+ });
2867
+ }
2868
+ async callTool(id, params) {
2869
+ if (!this.negotiatedProtocolVersion) {
2870
+ return jsonRpcError(id, -32002, "Server not initialized");
2871
+ }
2872
+ const parsed = safeParseWithErrors(
2873
+ z2.object({
2874
+ name: z2.string().min(1),
2875
+ arguments: z2.record(z2.string(), z2.unknown()).optional()
2876
+ }).strict(),
2877
+ params
2878
+ );
2879
+ if (!parsed.success) {
2880
+ return jsonRpcSuccess(id, toolArgumentError(parsed.errors));
2881
+ }
2882
+ const tool = this.toolMap.get(parsed.data.name);
2883
+ if (!tool) {
2884
+ return jsonRpcSuccess(
2885
+ id,
2886
+ toolResult(
2887
+ {
2888
+ success: false,
2889
+ revision: null,
2890
+ errors: [
2891
+ {
2892
+ code: "UNKNOWN_TOOL",
2893
+ message: `Unknown MCP tool: ${parsed.data.name}`
2894
+ }
2895
+ ],
2896
+ warnings: [],
2897
+ changedObjectIds: []
2898
+ },
2899
+ true
2900
+ )
2901
+ );
2902
+ }
2903
+ const validatedArguments = safeParseWithErrors(tool.schema, parsed.data.arguments ?? {});
2904
+ if (!validatedArguments.success) {
2905
+ return jsonRpcSuccess(id, toolArgumentError(validatedArguments.errors));
2906
+ }
2907
+ try {
2908
+ const payload = await tool.execute(validatedArguments.data, this.bridge);
2909
+ const resultPayload = typeof payload === "object" && payload !== null && "success" in payload ? payload : { success: true, data: payload };
2910
+ const isError = typeof resultPayload === "object" && resultPayload !== null && "success" in resultPayload && resultPayload.success === false;
2911
+ return jsonRpcSuccess(id, toolResult(resultPayload, isError));
2912
+ } catch (cause) {
2913
+ return jsonRpcSuccess(
2914
+ id,
2915
+ toolResult(
2916
+ {
2917
+ success: false,
2918
+ revision: null,
2919
+ errors: [
2920
+ {
2921
+ code: "MCP_TOOL_EXECUTION_FAILED",
2922
+ message: cause instanceof Error ? cause.message : String(cause)
2923
+ }
2924
+ ],
2925
+ warnings: [],
2926
+ changedObjectIds: []
2927
+ },
2928
+ true
2929
+ )
2930
+ );
2931
+ }
2932
+ }
2933
+ };
2934
+ var createDrowaiMcpServer = (options = {}) => DrowaiMcpServer.create(options);
2935
+
2936
+ import { createInterface } from "node:readline";
2937
+ var defaultRuntime = () => ({
2938
+ stdin: process.stdin,
2939
+ stdout: process.stdout,
2940
+ signalProcess: process,
2941
+ exit: (code) => {
2942
+ process.exit(code);
2943
+ }
2944
+ });
2945
+ var send = (output, message) => {
2946
+ output.write(`${JSON.stringify(message)}
2947
+ `);
2948
+ };
2949
+ var runStdioMcpServer = async (options, runtime = defaultRuntime()) => {
2950
+ const server = await createDrowaiMcpServer(options);
2951
+ const reader = createInterface({
2952
+ input: runtime.stdin,
2953
+ crlfDelay: Infinity
2954
+ });
2955
+ let closed = false;
2956
+ const closeServer = async () => {
2957
+ if (closed) {
2958
+ return;
2959
+ }
2960
+ closed = true;
2961
+ reader.close();
2962
+ await server.close();
2963
+ };
2964
+ const handleSignal = async () => {
2965
+ await closeServer();
2966
+ runtime.exit?.(0);
2967
+ };
2968
+ const signalProcess = runtime.signalProcess;
2969
+ signalProcess?.on("SIGINT", handleSignal);
2970
+ signalProcess?.on("SIGTERM", handleSignal);
2971
+ try {
2972
+ for await (const rawLine of reader) {
2973
+ const line = rawLine.trim();
2974
+ if (!line) {
2975
+ continue;
2976
+ }
2977
+ try {
2978
+ const message = JSON.parse(line);
2979
+ const response = await server.handleMessage(message);
2980
+ if (response) {
2981
+ send(runtime.stdout, response);
2982
+ }
2983
+ } catch (cause) {
2984
+ send(runtime.stdout, {
2985
+ jsonrpc: "2.0",
2986
+ id: null,
2987
+ error: {
2988
+ code: -32700,
2989
+ message: "Parse error",
2990
+ data: cause instanceof Error ? cause.message : String(cause)
2991
+ }
2992
+ });
2993
+ }
2994
+ }
2995
+ } finally {
2996
+ signalProcess?.off("SIGINT", handleSignal);
2997
+ signalProcess?.off("SIGTERM", handleSignal);
2998
+ await closeServer();
2999
+ }
3000
+ };
3001
+
3002
+ var defaultCliRuntime = () => ({
3003
+ stdin: process.stdin,
3004
+ stdout: process.stdout,
3005
+ stderr: process.stderr,
3006
+ signalProcess: process,
3007
+ exit: (code) => {
3008
+ process.exit(code);
3009
+ }
3010
+ });
3011
+ var usageText = () => [
3012
+ "Usage: drowai-mcp [--db-path <path>] [--auth-token <token>] [--help] [--version]",
3013
+ "",
3014
+ "MCP-only CLI for the DrowAI diagram platform.",
3015
+ "The package does not expose API or viewer subcommands.",
3016
+ "",
3017
+ "Options:",
3018
+ " --db-path <path> Persist SQLite data to the given path",
3019
+ " --auth-token <token> Override the embedded API auth secret",
3020
+ " --help Print this help text to stderr",
3021
+ " --version Print the CLI version to stderr",
3022
+ "",
3023
+ "Environment:",
3024
+ " DROWAI_DB_PATH Fallback database path when --db-path is omitted",
3025
+ " DROWAI_MCP_TOKEN Fallback auth token when --auth-token is omitted",
3026
+ "",
3027
+ "Examples:",
3028
+ " npx drowai-mcp",
3029
+ " DROWAI_DB_PATH=./state/drowai.sqlite npx drowai-mcp",
3030
+ " npx drowai-mcp --db-path ./state/drowai.sqlite"
3031
+ ].join("\n");
3032
+ var readInlineOrNext = (argv, index, name) => {
3033
+ const current = argv[index] ?? "";
3034
+ const inlinePrefix = `${name}=`;
3035
+ if (current.startsWith(inlinePrefix)) {
3036
+ const value = current.slice(inlinePrefix.length);
3037
+ return value ? { value, nextIndex: index } : { nextIndex: index, error: `Missing value for ${name}` };
3038
+ }
3039
+ const next = argv[index + 1];
3040
+ if (!next || next.startsWith("-")) {
3041
+ return { nextIndex: index, error: `Missing value for ${name}` };
3042
+ }
3043
+ return { value: next, nextIndex: index + 1 };
3044
+ };
3045
+ var parseCliArgs = (argv, env = process.env) => {
3046
+ let dbPath = env.DROWAI_DB_PATH;
3047
+ let authToken = env.DROWAI_MCP_TOKEN;
3048
+ for (let index = 0; index < argv.length; index += 1) {
3049
+ const arg = argv[index] ?? "";
3050
+ if (arg === "--help" || arg === "-h") {
3051
+ return { kind: "help" };
3052
+ }
3053
+ if (arg === "--version" || arg === "-v") {
3054
+ return { kind: "version" };
3055
+ }
3056
+ if (arg === "--db-path" || arg.startsWith("--db-path=")) {
3057
+ const parsed = readInlineOrNext(argv, index, "--db-path");
3058
+ if (parsed.error) {
3059
+ return { kind: "error", message: parsed.error };
3060
+ }
3061
+ dbPath = parsed.value;
3062
+ index = parsed.nextIndex;
3063
+ continue;
3064
+ }
3065
+ if (arg === "--auth-token" || arg.startsWith("--auth-token=")) {
3066
+ const parsed = readInlineOrNext(argv, index, "--auth-token");
3067
+ if (parsed.error) {
3068
+ return { kind: "error", message: parsed.error };
3069
+ }
3070
+ authToken = parsed.value;
3071
+ index = parsed.nextIndex;
3072
+ continue;
3073
+ }
3074
+ if (arg.startsWith("-")) {
3075
+ return { kind: "error", message: `Unknown argument: ${arg}` };
3076
+ }
3077
+ return { kind: "error", message: `Unexpected positional argument: ${arg}` };
3078
+ }
3079
+ return {
3080
+ kind: "run",
3081
+ serverOptions: {
3082
+ ...dbPath ? { dbPath } : {},
3083
+ ...authToken ? { authToken } : {}
3084
+ }
3085
+ };
3086
+ };
3087
+ var runCli = async (argv = process.argv.slice(2), runtime = defaultCliRuntime(), env = process.env) => {
3088
+ const command = parseCliArgs(argv, env);
3089
+ switch (command.kind) {
3090
+ case "help":
3091
+ runtime.stderr.write(`${usageText()}
3092
+ `);
3093
+ return 0;
3094
+ case "version":
3095
+ runtime.stderr.write(`${DROWAI_MCP_SERVER_VERSION}
3096
+ `);
3097
+ return 0;
3098
+ case "error":
3099
+ runtime.stderr.write(`${command.message}
3100
+
3101
+ ${usageText()}
3102
+ `);
3103
+ return 1;
3104
+ case "run":
3105
+ await runStdioMcpServer(command.serverOptions, runtime);
3106
+ return 0;
3107
+ }
3108
+ };
3109
+ var isCliEntrypoint = (entryUrl, argvPath) => {
3110
+ if (!argvPath) {
3111
+ return false;
3112
+ }
3113
+ try {
3114
+ return realpathSync(fileURLToPath(entryUrl)) === realpathSync(argvPath);
3115
+ } catch {
3116
+ return entryUrl === new URL(argvPath, "file://").href;
3117
+ }
3118
+ };
3119
+ if (isCliEntrypoint(import.meta.url, process.argv[1])) {
3120
+ const exitCode = await runCli();
3121
+ process.exitCode = exitCode;
3122
+ }
3123
+ export {
3124
+ parseCliArgs,
3125
+ runCli
3126
+ };