callman-core 1.29.0 → 1.31.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.
Files changed (218) hide show
  1. package/dist/ai/prompts/diagram.d.ts.map +1 -1
  2. package/dist/ai/prompts/diagram.js +24 -7
  3. package/dist/ai/prompts/diagram.js.map +1 -1
  4. package/dist/ai/types.d.ts +1 -1
  5. package/dist/ai/types.d.ts.map +1 -1
  6. package/dist/diagram/componentCatalog.d.ts +3 -0
  7. package/dist/diagram/componentCatalog.d.ts.map +1 -1
  8. package/dist/diagram/componentCatalog.js +5 -1
  9. package/dist/diagram/componentCatalog.js.map +1 -1
  10. package/dist/diagram/database/derive.d.ts +14 -0
  11. package/dist/diagram/database/derive.d.ts.map +1 -0
  12. package/dist/diagram/database/derive.js +114 -0
  13. package/dist/diagram/database/derive.js.map +1 -0
  14. package/dist/diagram/database/diff.d.ts +48 -0
  15. package/dist/diagram/database/diff.d.ts.map +1 -0
  16. package/dist/diagram/database/diff.js +109 -0
  17. package/dist/diagram/database/diff.js.map +1 -0
  18. package/dist/diagram/database/edit.d.ts +113 -0
  19. package/dist/diagram/database/edit.d.ts.map +1 -0
  20. package/dist/diagram/database/edit.js +359 -0
  21. package/dist/diagram/database/edit.js.map +1 -0
  22. package/dist/diagram/database/fromSchema.d.ts +12 -0
  23. package/dist/diagram/database/fromSchema.d.ts.map +1 -0
  24. package/dist/diagram/database/fromSchema.js +182 -0
  25. package/dist/diagram/database/fromSchema.js.map +1 -0
  26. package/dist/diagram/database/index.d.ts +9 -0
  27. package/dist/diagram/database/index.d.ts.map +1 -0
  28. package/dist/diagram/database/index.js +14 -0
  29. package/dist/diagram/database/index.js.map +1 -0
  30. package/dist/diagram/database/model.d.ts +93 -0
  31. package/dist/diagram/database/model.d.ts.map +1 -0
  32. package/dist/diagram/database/model.js +294 -0
  33. package/dist/diagram/database/model.js.map +1 -0
  34. package/dist/diagram/database/patch.d.ts +14 -0
  35. package/dist/diagram/database/patch.d.ts.map +1 -0
  36. package/dist/diagram/database/patch.js +186 -0
  37. package/dist/diagram/database/patch.js.map +1 -0
  38. package/dist/diagram/database/place.d.ts +4 -0
  39. package/dist/diagram/database/place.d.ts.map +1 -0
  40. package/dist/diagram/database/place.js +22 -0
  41. package/dist/diagram/database/place.js.map +1 -0
  42. package/dist/diagram/database/types.d.ts +50 -0
  43. package/dist/diagram/database/types.d.ts.map +1 -0
  44. package/dist/diagram/database/types.js +106 -0
  45. package/dist/diagram/database/types.js.map +1 -0
  46. package/dist/diagram/index.d.ts +1 -0
  47. package/dist/diagram/index.d.ts.map +1 -1
  48. package/dist/diagram/index.js +1 -0
  49. package/dist/diagram/index.js.map +1 -1
  50. package/dist/diagram/layout.d.ts +1 -0
  51. package/dist/diagram/layout.d.ts.map +1 -1
  52. package/dist/diagram/layout.js +2 -1
  53. package/dist/diagram/layout.js.map +1 -1
  54. package/dist/diagram/ops.d.ts +1 -1
  55. package/dist/diagram/ops.d.ts.map +1 -1
  56. package/dist/diagram/ops.js +42 -0
  57. package/dist/diagram/ops.js.map +1 -1
  58. package/dist/diagram/opsReference.d.ts +1 -1
  59. package/dist/diagram/opsReference.d.ts.map +1 -1
  60. package/dist/diagram/opsReference.js +9 -0
  61. package/dist/diagram/opsReference.js.map +1 -1
  62. package/dist/diagram/patchInput.d.ts +65 -1
  63. package/dist/diagram/patchInput.d.ts.map +1 -1
  64. package/dist/diagram/patchInput.js +414 -11
  65. package/dist/diagram/patchInput.js.map +1 -1
  66. package/dist/diagram/schema.d.ts +2 -2
  67. package/dist/diagram/schema.d.ts.map +1 -1
  68. package/dist/diagram/schema.js +3 -3
  69. package/dist/diagram/schema.js.map +1 -1
  70. package/dist/diagram/views.d.ts +13 -0
  71. package/dist/diagram/views.d.ts.map +1 -1
  72. package/dist/diagram/views.js +49 -0
  73. package/dist/diagram/views.js.map +1 -1
  74. package/dist/host/db/introspection/index.d.ts +9 -0
  75. package/dist/host/db/introspection/index.d.ts.map +1 -0
  76. package/dist/host/db/introspection/index.js +35 -0
  77. package/dist/host/db/introspection/index.js.map +1 -0
  78. package/dist/host/db/introspection/mongo.d.ts +7 -0
  79. package/dist/host/db/introspection/mongo.d.ts.map +1 -0
  80. package/dist/host/db/introspection/mongo.js +183 -0
  81. package/dist/host/db/introspection/mongo.js.map +1 -0
  82. package/dist/host/db/introspection/mssql.d.ts +6 -0
  83. package/dist/host/db/introspection/mssql.d.ts.map +1 -0
  84. package/dist/host/db/introspection/mssql.js +211 -0
  85. package/dist/host/db/introspection/mssql.js.map +1 -0
  86. package/dist/host/db/introspection/mysql.d.ts +5 -0
  87. package/dist/host/db/introspection/mysql.d.ts.map +1 -0
  88. package/dist/host/db/introspection/mysql.js +163 -0
  89. package/dist/host/db/introspection/mysql.js.map +1 -0
  90. package/dist/host/db/introspection/oracle.d.ts +12 -0
  91. package/dist/host/db/introspection/oracle.d.ts.map +1 -0
  92. package/dist/host/db/introspection/oracle.js +225 -0
  93. package/dist/host/db/introspection/oracle.js.map +1 -0
  94. package/dist/host/db/introspection/postgres.d.ts +5 -0
  95. package/dist/host/db/introspection/postgres.d.ts.map +1 -0
  96. package/dist/host/db/introspection/postgres.js +172 -0
  97. package/dist/host/db/introspection/postgres.js.map +1 -0
  98. package/dist/host/db/introspection/shared.d.ts +29 -0
  99. package/dist/host/db/introspection/shared.d.ts.map +1 -0
  100. package/dist/host/db/introspection/shared.js +119 -0
  101. package/dist/host/db/introspection/shared.js.map +1 -0
  102. package/dist/host/db/introspection/types.d.ts +58 -0
  103. package/dist/host/db/introspection/types.d.ts.map +1 -0
  104. package/dist/host/db/introspection/types.js +6 -0
  105. package/dist/host/db/introspection/types.js.map +1 -0
  106. package/dist/host/db/mongoAdapter.d.ts +1 -0
  107. package/dist/host/db/mongoAdapter.d.ts.map +1 -1
  108. package/dist/host/db/mongoAdapter.js +13 -0
  109. package/dist/host/db/mongoAdapter.js.map +1 -1
  110. package/dist/host/db/mssqlAdapter.d.ts +1 -0
  111. package/dist/host/db/mssqlAdapter.d.ts.map +1 -1
  112. package/dist/host/db/mssqlAdapter.js +3 -1
  113. package/dist/host/db/mssqlAdapter.js.map +1 -1
  114. package/dist/host/db/mysqlAdapter.d.ts +1 -0
  115. package/dist/host/db/mysqlAdapter.d.ts.map +1 -1
  116. package/dist/host/db/mysqlAdapter.js +11 -0
  117. package/dist/host/db/mysqlAdapter.js.map +1 -1
  118. package/dist/host/db/oracleAdapter.d.ts +1 -0
  119. package/dist/host/db/oracleAdapter.d.ts.map +1 -1
  120. package/dist/host/db/oracleAdapter.js +22 -0
  121. package/dist/host/db/oracleAdapter.js.map +1 -1
  122. package/dist/host/db/postgresAdapter.d.ts +1 -0
  123. package/dist/host/db/postgresAdapter.d.ts.map +1 -1
  124. package/dist/host/db/postgresAdapter.js +13 -0
  125. package/dist/host/db/postgresAdapter.js.map +1 -1
  126. package/dist/host/db/queryExecutor.d.ts +2 -1
  127. package/dist/host/db/queryExecutor.d.ts.map +1 -1
  128. package/dist/host/db/queryExecutor.js +1 -1
  129. package/dist/host/db/queryExecutor.js.map +1 -1
  130. package/dist/host/index.d.ts +7 -0
  131. package/dist/host/index.d.ts.map +1 -1
  132. package/dist/host/index.js +9 -0
  133. package/dist/host/index.js.map +1 -1
  134. package/dist/index.d.ts +2 -0
  135. package/dist/index.d.ts.map +1 -1
  136. package/dist/index.js +2 -0
  137. package/dist/index.js.map +1 -1
  138. package/dist/ui-test/commands.d.ts +54 -0
  139. package/dist/ui-test/commands.d.ts.map +1 -0
  140. package/dist/ui-test/commands.js +436 -0
  141. package/dist/ui-test/commands.js.map +1 -0
  142. package/dist/ui-test/flow-lint.d.ts +43 -0
  143. package/dist/ui-test/flow-lint.d.ts.map +1 -0
  144. package/dist/ui-test/flow-lint.js +229 -0
  145. package/dist/ui-test/flow-lint.js.map +1 -0
  146. package/dist-cjs/ai/prompts/diagram.js +23 -6
  147. package/dist-cjs/ai/prompts/diagram.js.map +1 -1
  148. package/dist-cjs/diagram/componentCatalog.js +5 -1
  149. package/dist-cjs/diagram/componentCatalog.js.map +1 -1
  150. package/dist-cjs/diagram/database/derive.js +119 -0
  151. package/dist-cjs/diagram/database/derive.js.map +1 -0
  152. package/dist-cjs/diagram/database/diff.js +115 -0
  153. package/dist-cjs/diagram/database/diff.js.map +1 -0
  154. package/dist-cjs/diagram/database/edit.js +366 -0
  155. package/dist-cjs/diagram/database/edit.js.map +1 -0
  156. package/dist-cjs/diagram/database/fromSchema.js +188 -0
  157. package/dist-cjs/diagram/database/fromSchema.js.map +1 -0
  158. package/dist-cjs/diagram/database/index.js +32 -0
  159. package/dist-cjs/diagram/database/index.js.map +1 -0
  160. package/dist-cjs/diagram/database/model.js +311 -0
  161. package/dist-cjs/diagram/database/model.js.map +1 -0
  162. package/dist-cjs/diagram/database/patch.js +190 -0
  163. package/dist-cjs/diagram/database/patch.js.map +1 -0
  164. package/dist-cjs/diagram/database/place.js +26 -0
  165. package/dist-cjs/diagram/database/place.js.map +1 -0
  166. package/dist-cjs/diagram/database/types.js +122 -0
  167. package/dist-cjs/diagram/database/types.js.map +1 -0
  168. package/dist-cjs/diagram/index.js +1 -0
  169. package/dist-cjs/diagram/index.js.map +1 -1
  170. package/dist-cjs/diagram/layout.js +3 -2
  171. package/dist-cjs/diagram/layout.js.map +1 -1
  172. package/dist-cjs/diagram/ops.js +42 -0
  173. package/dist-cjs/diagram/ops.js.map +1 -1
  174. package/dist-cjs/diagram/opsReference.js +9 -0
  175. package/dist-cjs/diagram/opsReference.js.map +1 -1
  176. package/dist-cjs/diagram/patchInput.js +414 -11
  177. package/dist-cjs/diagram/patchInput.js.map +1 -1
  178. package/dist-cjs/diagram/schema.js +3 -3
  179. package/dist-cjs/diagram/schema.js.map +1 -1
  180. package/dist-cjs/diagram/views.js +51 -1
  181. package/dist-cjs/diagram/views.js.map +1 -1
  182. package/dist-cjs/host/db/introspection/index.js +59 -0
  183. package/dist-cjs/host/db/introspection/index.js.map +1 -0
  184. package/dist-cjs/host/db/introspection/mongo.js +189 -0
  185. package/dist-cjs/host/db/introspection/mongo.js.map +1 -0
  186. package/dist-cjs/host/db/introspection/mssql.js +216 -0
  187. package/dist-cjs/host/db/introspection/mssql.js.map +1 -0
  188. package/dist-cjs/host/db/introspection/mysql.js +167 -0
  189. package/dist-cjs/host/db/introspection/mysql.js.map +1 -0
  190. package/dist-cjs/host/db/introspection/oracle.js +230 -0
  191. package/dist-cjs/host/db/introspection/oracle.js.map +1 -0
  192. package/dist-cjs/host/db/introspection/postgres.js +176 -0
  193. package/dist-cjs/host/db/introspection/postgres.js.map +1 -0
  194. package/dist-cjs/host/db/introspection/shared.js +133 -0
  195. package/dist-cjs/host/db/introspection/shared.js.map +1 -0
  196. package/dist-cjs/host/db/introspection/types.js +7 -0
  197. package/dist-cjs/host/db/introspection/types.js.map +1 -0
  198. package/dist-cjs/host/db/mongoAdapter.js +15 -1
  199. package/dist-cjs/host/db/mongoAdapter.js.map +1 -1
  200. package/dist-cjs/host/db/mssqlAdapter.js +5 -2
  201. package/dist-cjs/host/db/mssqlAdapter.js.map +1 -1
  202. package/dist-cjs/host/db/mysqlAdapter.js +13 -1
  203. package/dist-cjs/host/db/mysqlAdapter.js.map +1 -1
  204. package/dist-cjs/host/db/oracleAdapter.js +24 -1
  205. package/dist-cjs/host/db/oracleAdapter.js.map +1 -1
  206. package/dist-cjs/host/db/postgresAdapter.js +15 -1
  207. package/dist-cjs/host/db/postgresAdapter.js.map +1 -1
  208. package/dist-cjs/host/db/queryExecutor.js +5 -4
  209. package/dist-cjs/host/db/queryExecutor.js.map +1 -1
  210. package/dist-cjs/host/index.js +24 -1
  211. package/dist-cjs/host/index.js.map +1 -1
  212. package/dist-cjs/index.js +2 -0
  213. package/dist-cjs/index.js.map +1 -1
  214. package/dist-cjs/ui-test/commands.js +441 -0
  215. package/dist-cjs/ui-test/commands.js.map +1 -0
  216. package/dist-cjs/ui-test/flow-lint.js +235 -0
  217. package/dist-cjs/ui-test/flow-lint.js.map +1 -0
  218. package/package.json +3 -1
@@ -5,6 +5,7 @@
5
5
  // by the MCP server (behind its zod schemas), the AI EDIT mode and imports.
6
6
  // Moved from the MCP server's build.ts / patch.ts.
7
7
  import { BPMN_CONTAINER_TYPES, bpmnElementForType, getBpmnType, isBpmnContainerType, suggestBpmnTypes } from "./bpmn/types.js";
8
+ import { applyDatabaseEdits, DATABASE_GRAMMAR_VERSION, DatabaseEditError, databaseEntitySize, DB_NODE_TYPES, emptyDatabaseModel, findEntity, isValidCardinality, kindForDatabaseType, markersForCardinality, parseDatabaseModel, serializeDatabaseModel, suggestDatabaseTypes, } from "./database/index.js";
8
9
  import { DIAGRAM_COMPONENT_CATALOG, getDiagramComponent, searchDiagramComponents } from "./componentCatalog.js";
9
10
  import { isBusComponentType, isDefaultEdgeStyle, semanticStyle } from "./edgeSemantics.js";
10
11
  import { absolutePosition, absoluteRect, boundingBox, indexNodes, toParentRelative } from "./geometry.js";
@@ -52,6 +53,8 @@ export const isContainerType = (type) => {
52
53
  };
53
54
  export const containerTypes = () => DIAGRAM_COMPONENT_CATALOG.filter((entry) => entry.container || entry.swimlane).map((entry) => entry.type);
54
55
  export const parentWarning = (id, parentId, parentType, kind = "canvas") => {
56
+ if (kind === "database")
57
+ return `Node ${id} sits inside "${parentId}" — database diagrams have no containers; the parent is ignored.`;
55
58
  if (kind === "bpmn") {
56
59
  return isBpmnContainerType(parentType)
57
60
  ? null
@@ -77,6 +80,16 @@ export const edgeStyleFor = (semantic, color) => {
77
80
  };
78
81
  const resolveType = (type, id, strict, warnings, opIndex, kind = "canvas") => {
79
82
  const trimmed = type.trim();
83
+ if (kind === "database") {
84
+ if (DB_NODE_TYPES.includes(trimmed))
85
+ return { type: trimmed, known: true };
86
+ const suggestions = suggestDatabaseTypes(trimmed);
87
+ if (strict) {
88
+ throw new DiagramOpError("DIAGRAM_UNKNOWN_COMPONENT_TYPE", `Unknown database type "${trimmed}" on ${id}. A database diagram has db.table, db.view and db.collection (prefer the DATABASE OPS: addTable, addColumn, addRelationship …), or pass strictTypes:false to draw it as a table.`, opIndex, { unknownTypes: [{ type: trimmed, suggestions }] });
89
+ }
90
+ warnings.push(`Unknown database type "${trimmed}" on ${id} → drawn as a table`);
91
+ return { type: "db.table", known: false };
92
+ }
80
93
  if (kind === "bpmn") {
81
94
  if (getBpmnType(trimmed))
82
95
  return { type: trimmed, known: true };
@@ -100,18 +113,38 @@ const resolveType = (type, id, strict, warnings, opIndex, kind = "canvas") => {
100
113
  };
101
114
  const nodeFromInput = (input, id, strict, warnings, opIndex, kind = "canvas") => {
102
115
  const { type, known } = resolveType(input.type, id, strict, warnings, opIndex, kind);
103
- const def = kind === "bpmn" ? undefined : getDiagramComponent(type);
116
+ const def = kind === "canvas" ? getDiagramComponent(type) : undefined;
104
117
  const bpmn = kind === "bpmn" ? getBpmnType(type) : undefined;
105
- const label = input.label !== undefined || !known ? (input.label ?? input.type) : def?.defaultLabel;
106
- // A BPMN node always knows which element it stands for.
118
+ const dbRaw = kind === "database" ? (input.data?.db ?? {}) : undefined;
119
+ const label = kind === "database"
120
+ ? (typeof dbRaw?.name === "string" && dbRaw.name.trim()) || input.label || id
121
+ : input.label !== undefined || !known
122
+ ? (input.label ?? input.type)
123
+ : def?.defaultLabel;
124
+ // A BPMN node always knows which element it stands for; a database node its
125
+ // table name and kind (its `columns` stay the authoring input until the
126
+ // model absorbs them).
107
127
  const data = kind === "bpmn"
108
128
  ? { ...(input.data ?? {}), bpmn: { ...(input.data?.bpmn ?? {}), element: bpmnElementForType(type) ?? "task" } }
109
- : input.data;
129
+ : kind === "database"
130
+ ? { ...(input.data ?? {}), db: { ...dbRaw, name: label, kind: kindForDatabaseType(type) } }
131
+ : input.data;
132
+ const dbColumns = Array.isArray(dbRaw?.columns) ? dbRaw.columns : [];
133
+ const dbSize = kind === "database"
134
+ ? databaseEntitySize({
135
+ name: String(label),
136
+ ...(typeof dbRaw?.schema === "string" ? { schema: dbRaw.schema } : {}),
137
+ columns: dbColumns.map((column) => {
138
+ const raw = (column ?? {});
139
+ return { name: typeof raw.name === "string" ? raw.name : "", dataType: typeof raw.dataType === "string" ? raw.dataType : typeof raw.type === "string" ? raw.type : "" };
140
+ }),
141
+ })
142
+ : undefined;
110
143
  const node = {
111
144
  id,
112
145
  type,
113
146
  position: input.position ?? { x: 0, y: 0 },
114
- size: input.size ?? bpmn?.size ?? def?.defaultSize ?? { width: 140, height: 60 },
147
+ size: input.size ?? bpmn?.size ?? dbSize ?? def?.defaultSize ?? { width: 140, height: 60 },
115
148
  ...(label !== undefined ? { label } : {}),
116
149
  ...(input.parent ? { parentId: input.parent } : {}),
117
150
  ...(input.style ? { style: applyStyleInput(undefined, input.style) } : {}),
@@ -130,19 +163,26 @@ const edgeFromInput = (input, id, typeOf, kind = "canvas") => {
130
163
  // A fresh edge into a broker reads as a bus line — the studio's own rule.
131
164
  if (kind === "canvas" && !semantic && (isBusComponentType(typeOf(input.source)) || isBusComponentType(typeOf(input.target))))
132
165
  semantic = "bus";
133
- // A BPMN flow is a sequence flow unless said otherwise.
166
+ // A BPMN flow is a sequence flow unless said otherwise; a database edge is
167
+ // a relationship (manual, N:1 unless said otherwise) with crow's-foot ends.
168
+ const dbRaw = kind === "database" ? (input.data?.db ?? {}) : undefined;
169
+ const cardinality = isValidCardinality(dbRaw?.cardinality) ? dbRaw.cardinality : "N:1";
134
170
  const data = kind === "bpmn"
135
171
  ? { ...(input.data ?? {}), bpmn: { flow: "sequence", ...(input.data?.bpmn ?? {}) } }
136
- : input.data;
172
+ : kind === "database"
173
+ ? { ...(input.data ?? {}), db: { kind: "manual", sourceColumns: [], targetColumns: [], ...dbRaw, cardinality } }
174
+ : input.data;
175
+ const dbMarkers = kind === "database" ? markersForCardinality(cardinality) : undefined;
137
176
  const style = edgeStyleFor(semantic, input.color);
177
+ const markerStart = input.markerStart ?? dbMarkers?.markerStart;
138
178
  return {
139
179
  id,
140
180
  source: input.source,
141
181
  target: input.target,
142
182
  ...(input.label ? { label: input.label } : {}),
143
183
  ...(input.routing ? { routing: input.routing } : {}),
144
- markerEnd: input.markerEnd ?? "classicThin",
145
- ...(input.markerStart && input.markerStart !== "none" ? { markerStart: input.markerStart } : {}),
184
+ markerEnd: input.markerEnd ?? dbMarkers?.markerEnd ?? "classicThin",
185
+ ...(markerStart && markerStart !== "none" ? { markerStart } : {}),
146
186
  ...(input.sourceSide ? { sourceHandle: input.sourceSide } : {}),
147
187
  ...(input.targetSide ? { targetHandle: input.targetSide } : {}),
148
188
  ...(style && (!isDefaultEdgeStyle(style) || semantic === "sync" || input.color) ? { style } : {}),
@@ -152,7 +192,9 @@ const edgeFromInput = (input, id, typeOf, kind = "canvas") => {
152
192
  // --- lowering -----------------------------------------------------------------------
153
193
  export const lowerPatchOps = (doc, input, options = {}) => {
154
194
  const strict = options.strictTypes !== false;
155
- const kind = options.kind ?? (doc.source?.format === "bpmn" ? "bpmn" : "canvas");
195
+ const kind = options.kind ?? (doc.source?.format === "bpmn" ? "bpmn" : doc.source?.format === "database" ? "database" : "canvas");
196
+ if (kind === "database")
197
+ return lowerDatabasePatchOps(doc, input, { ...options, strict });
156
198
  const warnings = [];
157
199
  const unpositionedNodeIds = [];
158
200
  const idMap = {};
@@ -442,12 +484,373 @@ export const lowerPatchOps = (doc, input, options = {}) => {
442
484
  case "relayout":
443
485
  relayout = { scope: op.scope, ...(op.direction ? { direction: op.direction } : {}) };
444
486
  return;
487
+ case "addTable":
488
+ case "renameTable":
489
+ case "removeTable":
490
+ case "setTableComment":
491
+ case "addColumn":
492
+ case "alterColumn":
493
+ case "removeColumn":
494
+ case "setPrimaryKey":
495
+ case "addRelationship":
496
+ case "updateRelationship":
497
+ case "removeRelationship":
498
+ case "hideTable":
499
+ case "showTable":
500
+ throw new DiagramOpError("DIAGRAM_INVALID_OP", `"${op.op}" is a database-diagram op; this diagram is a ${kind} diagram.`, index);
445
501
  default:
446
502
  throw new DiagramOpError("DIAGRAM_INVALID_OP", `Unknown op "${op.op}"`, index);
447
503
  }
448
504
  });
449
505
  return { ops, warnings, unpositionedNodeIds, ...(relayout ? { relayout } : {}), idMap };
450
506
  };
507
+ // --- database diagrams -------------------------------------------------------------
508
+ //
509
+ // Every structural op (semantic or generic) becomes an edit of the database
510
+ // model; the batch then yields ONE setSource op (the model re-derives the
511
+ // nodes/edges) followed by the presentation ops (positions, visibility,
512
+ // routing) that address nodes by their entity ids.
513
+ const DATABASE_ERROR_CODES = {
514
+ DIAGRAM_TABLE_NOT_FOUND: "DIAGRAM_TABLE_NOT_FOUND",
515
+ DIAGRAM_COLUMN_NOT_FOUND: "DIAGRAM_COLUMN_NOT_FOUND",
516
+ DIAGRAM_RELATIONSHIP_NOT_FOUND: "DIAGRAM_RELATIONSHIP_NOT_FOUND",
517
+ DIAGRAM_DUPLICATE_TABLE: "DIAGRAM_DUPLICATE_TABLE",
518
+ DIAGRAM_DUPLICATE_COLUMN: "DIAGRAM_DUPLICATE_COLUMN",
519
+ DIAGRAM_INVALID_OP: "DIAGRAM_INVALID_OP",
520
+ };
521
+ const columnInputsOf = (value) => {
522
+ if (!Array.isArray(value))
523
+ return undefined;
524
+ return value
525
+ .filter((item) => Boolean(item) && typeof item === "object" && typeof item.name === "string")
526
+ .map((item) => ({
527
+ ...(typeof item.id === "string" ? { id: item.id } : {}),
528
+ name: String(item.name),
529
+ dataType: typeof item.dataType === "string" ? item.dataType : typeof item.type === "string" ? item.type : "",
530
+ ...(typeof item.nullable === "boolean" ? { nullable: item.nullable } : {}),
531
+ ...(typeof item.primaryKey === "boolean" ? { primaryKey: item.primaryKey } : {}),
532
+ ...(typeof item.unique === "boolean" ? { unique: item.unique } : {}),
533
+ ...(item.defaultValue !== undefined && item.defaultValue !== null ? { defaultValue: String(item.defaultValue) } : {}),
534
+ ...(typeof item.autoIncrement === "boolean" ? { autoIncrement: item.autoIncrement } : {}),
535
+ ...(typeof item.comment === "string" ? { comment: item.comment } : {}),
536
+ }));
537
+ };
538
+ const lowerDatabasePatchOps = (doc, input, options) => {
539
+ const warnings = [];
540
+ const idMap = {};
541
+ const presentation = [];
542
+ const positioned = new Map();
543
+ const unpositionedNodeIds = [];
544
+ let relayout;
545
+ const parsed = doc.source?.format === "database" ? parseDatabaseModel(doc.source.text) : null;
546
+ if (parsed && !parsed.ok)
547
+ throw new DiagramOpError("DIAGRAM_DOCUMENT_INVALID", `The database model cannot be read: ${parsed.errors.join("; ")}`);
548
+ let model = parsed?.model ?? emptyDatabaseModel();
549
+ let changed = false;
550
+ const mapId = (id) => idMap[id] ?? id;
551
+ const run = (edits, index) => {
552
+ try {
553
+ const result = applyDatabaseEdits(model, edits);
554
+ model = result.model;
555
+ changed = changed || result.changed;
556
+ warnings.push(...result.warnings);
557
+ Object.assign(idMap, result.idMap);
558
+ return result;
559
+ }
560
+ catch (error) {
561
+ if (error instanceof DatabaseEditError)
562
+ throw new DiagramOpError(DATABASE_ERROR_CODES[error.code], error.message, index);
563
+ throw error;
564
+ }
565
+ };
566
+ const entityIdOf = (ref, index) => {
567
+ const entity = findEntity(model, mapId(ref));
568
+ if (!entity)
569
+ throw new DiagramOpError("DIAGRAM_TABLE_NOT_FOUND", `Table "${ref}" does not exist.`, index);
570
+ return entity.id;
571
+ };
572
+ const relationshipIdOf = (ref) => mapId(ref);
573
+ const noContainers = (index) => {
574
+ throw new DiagramOpError("DIAGRAM_INVALID_OP", "Database diagrams have no containers (group / ungroup / reparent do not apply).", index);
575
+ };
576
+ const endInput = (value) => (typeof value === "string" ? value : { ...value, table: mapId(value.table) });
577
+ input.forEach((op, index) => {
578
+ switch (op.op) {
579
+ case "setTitle":
580
+ presentation.push({ op: "setTitle", title: op.title });
581
+ return;
582
+ case "addTable": {
583
+ const result = run([{ kind: "addTable", table: op.table }], index);
584
+ const id = result.addedEntityIds[0];
585
+ if (op.position)
586
+ positioned.set(id, op.position);
587
+ else
588
+ unpositionedNodeIds.push(id);
589
+ return;
590
+ }
591
+ case "renameTable":
592
+ run([{ kind: "renameTable", table: mapId(op.table), name: op.name, ...(op.schema !== undefined ? { schema: op.schema } : {}) }], index);
593
+ return;
594
+ case "removeTable":
595
+ run([{ kind: "removeTable", table: mapId(op.table) }], index);
596
+ return;
597
+ case "setTableComment":
598
+ run([{ kind: "setTableComment", table: mapId(op.table), comment: op.comment }], index);
599
+ return;
600
+ case "addColumn":
601
+ run([{ kind: "addColumn", table: mapId(op.table), column: op.column, ...(op.after !== undefined ? { after: op.after } : {}) }], index);
602
+ return;
603
+ case "alterColumn":
604
+ run([{ kind: "alterColumn", table: mapId(op.table), column: op.column, changes: op.changes }], index);
605
+ return;
606
+ case "removeColumn":
607
+ run([{ kind: "removeColumn", table: mapId(op.table), column: op.column }], index);
608
+ return;
609
+ case "setPrimaryKey":
610
+ run([{ kind: "setPrimaryKey", table: mapId(op.table), columns: op.columns }], index);
611
+ return;
612
+ case "addRelationship":
613
+ run([
614
+ {
615
+ kind: "addRelationship",
616
+ ...(op.id ? { id: op.id } : {}),
617
+ source: endInput(op.source),
618
+ target: endInput(op.target),
619
+ ...(op.cardinality ? { cardinality: op.cardinality } : {}),
620
+ ...(op.kind ? { relationshipKind: op.kind } : {}),
621
+ ...(op.name ? { name: op.name } : {}),
622
+ ...(op.label ? { label: op.label } : {}),
623
+ },
624
+ ], index);
625
+ return;
626
+ case "updateRelationship": {
627
+ const { kind: relationshipKind, source, target, ...rest } = op.changes;
628
+ run([
629
+ {
630
+ kind: "updateRelationship",
631
+ relationship: relationshipIdOf(op.relationship),
632
+ changes: { ...rest, ...(source !== undefined ? { source: endInput(source) } : {}), ...(target !== undefined ? { target: endInput(target) } : {}), ...(relationshipKind ? { relationshipKind } : {}) },
633
+ },
634
+ ], index);
635
+ return;
636
+ }
637
+ case "removeRelationship":
638
+ run([{ kind: "removeRelationship", relationship: relationshipIdOf(op.relationship) }], index);
639
+ return;
640
+ case "hideTable":
641
+ presentation.push({ op: "updateNode", id: entityIdOf(op.table, index), changes: { hidden: true } });
642
+ return;
643
+ case "showTable":
644
+ presentation.push({ op: "updateNode", id: entityIdOf(op.table, index), changes: { hidden: false } });
645
+ return;
646
+ case "addNode": {
647
+ const { type } = resolveType(op.node.type, op.node.id ?? op.node.label ?? "node", options.strict, warnings, index, "database");
648
+ if (op.node.parent)
649
+ warnings.push(parentWarning(op.node.id ?? "node", op.node.parent, undefined, "database") ?? "");
650
+ const raw = op.node.data?.db ?? {};
651
+ const name = (typeof raw.name === "string" && raw.name.trim()) || (op.node.label ?? "").trim() || op.node.id?.trim() || "table";
652
+ const result = run([
653
+ {
654
+ kind: "addTable",
655
+ table: {
656
+ ...(op.node.id?.trim() ? { id: op.node.id.trim() } : {}),
657
+ name,
658
+ ...(typeof raw.schema === "string" && raw.schema.trim() ? { schema: raw.schema.trim() } : {}),
659
+ kind: kindForDatabaseType(type),
660
+ ...(columnInputsOf(raw.columns ?? raw.columnInputs) ? { columns: columnInputsOf(raw.columns ?? raw.columnInputs) } : {}),
661
+ ...(typeof raw.comment === "string" && raw.comment.trim() ? { comment: raw.comment.trim() } : {}),
662
+ },
663
+ },
664
+ ], index);
665
+ const id = result.addedEntityIds[0];
666
+ if (op.node.position)
667
+ positioned.set(id, op.node.position);
668
+ else
669
+ unpositionedNodeIds.push(id);
670
+ if (op.node.hidden)
671
+ presentation.push({ op: "updateNode", id, changes: { hidden: true } });
672
+ if (op.node.collapsed)
673
+ presentation.push({ op: "updateNode", id, changes: { collapsed: true } });
674
+ if (op.node.locked)
675
+ presentation.push({ op: "updateNode", id, changes: { locked: true } });
676
+ if (op.node.style)
677
+ presentation.push({ op: "updateNode", id, changes: { style: styleInputToChanges(undefined, op.node.style) } });
678
+ if (op.node.size)
679
+ presentation.push({ op: "resizeNode", id, size: op.node.size });
680
+ return;
681
+ }
682
+ case "updateNode": {
683
+ const id = entityIdOf(op.id, index);
684
+ const changes = op.changes;
685
+ if (changes.parent)
686
+ noContainers(index);
687
+ const edits = [];
688
+ if (changes.type !== undefined) {
689
+ const { type } = resolveType(changes.type, id, options.strict, warnings, index, "database");
690
+ edits.push({ kind: "setTableKind", table: id, entityKind: kindForDatabaseType(type) });
691
+ }
692
+ const raw = changes.data && typeof changes.data.db === "object" && changes.data.db ? changes.data.db : undefined;
693
+ const requestedName = typeof raw?.name === "string" ? raw.name : typeof changes.label === "string" ? changes.label : undefined;
694
+ if (requestedName !== undefined) {
695
+ const dot = requestedName.lastIndexOf(".");
696
+ const schema = typeof raw?.schema === "string" ? raw.schema : dot > 0 ? requestedName.slice(0, dot) : undefined;
697
+ edits.push({ kind: "renameTable", table: id, name: dot > 0 && typeof raw?.name !== "string" ? requestedName.slice(dot + 1) : requestedName, ...(schema !== undefined ? { schema } : {}) });
698
+ }
699
+ else if (typeof raw?.schema === "string") {
700
+ const entity = findEntity(model, id);
701
+ if (entity)
702
+ edits.push({ kind: "renameTable", table: id, name: entity.name, schema: raw.schema || null });
703
+ }
704
+ if (raw && "comment" in raw)
705
+ edits.push({ kind: "setTableComment", table: id, comment: typeof raw.comment === "string" ? raw.comment : null });
706
+ const columns = raw ? columnInputsOf(raw.columns ?? raw.columnInputs) : undefined;
707
+ if (columns)
708
+ edits.push({ kind: "setColumns", table: id, columns });
709
+ if (edits.length > 0)
710
+ run(edits, index);
711
+ const nodeChanges = {};
712
+ if (changes.rotation !== undefined)
713
+ nodeChanges.rotation = changes.rotation;
714
+ if (changes.locked !== undefined)
715
+ nodeChanges.locked = changes.locked;
716
+ if (changes.hidden !== undefined)
717
+ nodeChanges.hidden = changes.hidden;
718
+ if (changes.collapsed !== undefined)
719
+ nodeChanges.collapsed = changes.collapsed;
720
+ if (changes.style !== undefined)
721
+ nodeChanges.style = changes.style === null ? null : styleInputToChanges(doc.nodes.find((node) => node.id === id)?.style, changes.style);
722
+ if (Object.keys(nodeChanges).length > 0)
723
+ presentation.push({ op: "updateNode", id, changes: nodeChanges });
724
+ if (changes.size !== undefined)
725
+ presentation.push({ op: "resizeNode", id, size: changes.size });
726
+ if (changes.position !== undefined)
727
+ presentation.push({ op: "moveNodes", moves: [{ id, position: changes.position }] });
728
+ return;
729
+ }
730
+ case "removeNode":
731
+ run([{ kind: "removeTable", table: entityIdOf(op.id, index) }], index);
732
+ return;
733
+ case "addEdge": {
734
+ const raw = op.edge.data?.db ?? {};
735
+ const refs = (value) => (Array.isArray(value) ? value.filter((item) => typeof item === "string") : []);
736
+ const result = run([
737
+ {
738
+ kind: "addRelationship",
739
+ ...(op.edge.id?.trim() ? { id: op.edge.id.trim() } : {}),
740
+ source: { table: entityIdOf(op.edge.source, index), columns: refs(raw.sourceColumns) },
741
+ target: { table: entityIdOf(op.edge.target, index), columns: refs(raw.targetColumns) },
742
+ ...(isValidCardinality(raw.cardinality) ? { cardinality: raw.cardinality } : {}),
743
+ relationshipKind: raw.kind === "foreignKey" ? "foreignKey" : "manual",
744
+ ...(typeof raw.name === "string" ? { name: raw.name } : {}),
745
+ ...(op.edge.label ? { label: op.edge.label } : {}),
746
+ },
747
+ ], index);
748
+ const id = result.addedRelationshipIds[0];
749
+ if (id && (op.edge.routing || op.edge.sourceSide || op.edge.targetSide || op.edge.color)) {
750
+ const edgeChanges = {};
751
+ if (op.edge.routing)
752
+ edgeChanges.routing = op.edge.routing;
753
+ if (op.edge.sourceSide)
754
+ edgeChanges.sourceHandle = op.edge.sourceSide;
755
+ if (op.edge.targetSide)
756
+ edgeChanges.targetHandle = op.edge.targetSide;
757
+ if (op.edge.color)
758
+ edgeChanges.style = { stroke: toTokenRef(op.edge.color) };
759
+ presentation.push({ op: "updateEdge", id, changes: edgeChanges });
760
+ }
761
+ return;
762
+ }
763
+ case "updateEdge": {
764
+ const id = relationshipIdOf(op.id);
765
+ const changes = op.changes;
766
+ const raw = changes.data && typeof changes.data.db === "object" && changes.data.db ? changes.data.db : undefined;
767
+ const refs = (value) => (Array.isArray(value) ? value.filter((item) => typeof item === "string") : undefined);
768
+ const relationshipChanges = {};
769
+ if (changes.source !== undefined || refs(raw?.sourceColumns))
770
+ relationshipChanges.source = { table: changes.source !== undefined ? entityIdOf(changes.source, index) : (findEntity(model, id) ? model.relationships.find((r) => r.id === id)?.source.entityId ?? "" : ""), columns: refs(raw?.sourceColumns) ?? (changes.source !== undefined ? [] : undefined) };
771
+ if (changes.target !== undefined || refs(raw?.targetColumns))
772
+ relationshipChanges.target = { table: changes.target !== undefined ? entityIdOf(changes.target, index) : model.relationships.find((r) => r.id === id)?.target.entityId ?? "", columns: refs(raw?.targetColumns) ?? (changes.target !== undefined ? [] : undefined) };
773
+ if (isValidCardinality(raw?.cardinality))
774
+ relationshipChanges.cardinality = raw.cardinality;
775
+ if (raw && (raw.kind === "manual" || raw.kind === "foreignKey"))
776
+ relationshipChanges.relationshipKind = raw.kind;
777
+ if (raw && "name" in raw)
778
+ relationshipChanges.name = typeof raw.name === "string" ? raw.name : null;
779
+ if (changes.label !== undefined)
780
+ relationshipChanges.label = changes.label === "" ? null : changes.label;
781
+ if (Object.keys(relationshipChanges).length > 0)
782
+ run([{ kind: "updateRelationship", relationship: id, changes: relationshipChanges }], index);
783
+ const edgeChanges = {};
784
+ if (changes.routing !== undefined)
785
+ edgeChanges.routing = changes.routing;
786
+ if (changes.sourceSide !== undefined)
787
+ edgeChanges.sourceHandle = changes.sourceSide;
788
+ if (changes.targetSide !== undefined)
789
+ edgeChanges.targetHandle = changes.targetSide;
790
+ if (changes.color !== undefined)
791
+ edgeChanges.style = { stroke: changes.color === null ? null : toTokenRef(changes.color) };
792
+ if (Object.keys(edgeChanges).length > 0)
793
+ presentation.push({ op: "updateEdge", id, changes: edgeChanges });
794
+ return;
795
+ }
796
+ case "removeEdge":
797
+ run([{ kind: "removeRelationship", relationship: relationshipIdOf(op.id) }], index);
798
+ return;
799
+ case "moveNode":
800
+ presentation.push({ op: "moveNodes", moves: [{ id: entityIdOf(op.id, index), position: op.position }] });
801
+ return;
802
+ case "setEdgeRoute": {
803
+ const changes = {};
804
+ if (op.routing !== undefined)
805
+ changes.routing = op.routing;
806
+ if (op.sourceSide !== undefined)
807
+ changes.sourceHandle = op.sourceSide;
808
+ if (op.targetSide !== undefined)
809
+ changes.targetHandle = op.targetSide;
810
+ if (op.waypoints !== undefined)
811
+ changes.waypoints = op.waypoints;
812
+ if (Object.keys(changes).length === 0) {
813
+ changes.waypoints = null;
814
+ changes.sourceAnchor = null;
815
+ changes.targetAnchor = null;
816
+ }
817
+ presentation.push({ op: "updateEdge", id: relationshipIdOf(op.id), changes });
818
+ return;
819
+ }
820
+ case "reorder": {
821
+ const id = entityIdOf(op.id, index);
822
+ const count = doc.nodes.filter((node) => !node.parentId).length;
823
+ presentation.push({ op: "reorder", kind: "node", id, index: op.index === "front" ? Math.max(0, count - 1) : op.index === "back" ? 0 : op.index });
824
+ return;
825
+ }
826
+ case "setCanvas":
827
+ presentation.push({ op: "setCanvas", changes: op.canvas });
828
+ return;
829
+ case "relayout":
830
+ relayout = { scope: op.scope, ...(op.direction ? { direction: op.direction } : {}) };
831
+ return;
832
+ case "reparent":
833
+ case "group":
834
+ case "ungroup":
835
+ noContainers(index);
836
+ return;
837
+ default:
838
+ throw new DiagramOpError("DIAGRAM_INVALID_OP", `Unknown op "${op.op}"`, index);
839
+ }
840
+ });
841
+ const ops = [];
842
+ if (changed) {
843
+ ops.push({
844
+ op: "setSource",
845
+ source: { format: "database", text: serializeDatabaseModel(model), diagramType: "database", grammar: DATABASE_GRAMMAR_VERSION, valid: true },
846
+ resync: true,
847
+ });
848
+ }
849
+ for (const [id, position] of positioned)
850
+ ops.push({ op: "moveNodes", moves: [{ id, position }] });
851
+ ops.push(...presentation);
852
+ return { ops, warnings: warnings.filter(Boolean), unpositionedNodeIds, ...(relayout ? { relayout } : {}), idMap };
853
+ };
451
854
  export const buildDiagramFromInput = (input, options = {}) => {
452
855
  const kind = options.kind ?? "canvas";
453
856
  const strict = options.strictTypes !== false;
@@ -476,7 +879,7 @@ export const buildDiagramFromInput = (input, options = {}) => {
476
879
  const entry = error.data?.unknownTypes?.[0];
477
880
  if (entry)
478
881
  unknown.push(entry);
479
- return nodeFromInput({ ...node, type: kind === "bpmn" ? "bpmn.task" : "rect", label: node.label ?? node.type }, id, false, [], index, kind);
882
+ return nodeFromInput({ ...node, type: kind === "bpmn" ? "bpmn.task" : kind === "database" ? "db.table" : "rect", label: node.label ?? node.type }, id, false, [], index, kind);
480
883
  }
481
884
  throw error;
482
885
  }