@erd-studio/renderer 0.1.2 → 0.1.3
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/README.md +19 -1
- package/dist/{chunk-5OQLRX7R.js → chunk-H2ACJRPZ.js} +174 -47
- package/dist/editor.js +21 -1
- package/dist/index.js +13 -1
- package/dist/styles.css +23 -0
- package/dist/types/editor.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/lib/badgeLabels.d.ts +6 -0
- package/dist/types/lib/dbtKeyTitle.d.ts +7 -0
- package/dist/types/lib/relationshipDisplayKey.d.ts +37 -0
- package/dist/types/types/graph.d.ts +17 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Peer dependencies: `react` and `react-dom` 18.3+, `@xyflow/react` 12.4+.
|
|
|
8
8
|
|
|
9
9
|
| Import | What it is for |
|
|
10
10
|
|---|---|
|
|
11
|
-
| `@erd-studio/renderer` | Showing a domain: the `ErdCanvas` viewer component (below), plus `transformDomain` (a `DisplayDomain` to React Flow nodes and edges), `pickHandleSides`, `repickHandleSides` / `nodeRect`, and the display, domain and graph types (`DisplayDomain`, `ModelFlowNode`, `FkFlowEdge`, …). |
|
|
11
|
+
| `@erd-studio/renderer` | Showing a domain: the `ErdCanvas` viewer component (below), plus `transformDomain` (a `DisplayDomain` to React Flow nodes and edges), `pickHandleSides`, `repickHandleSides` / `nodeRect`, the diagram exporters re-exported from `@erd-studio/core` (below), and the display, domain and graph types (`DisplayDomain`, `ModelFlowNode`, `FkFlowEdge`, …). |
|
|
12
12
|
| `@erd-studio/renderer/editor` | Everything above plus what a host needs to build a full editor around the canvas, as the extension's webview does: the store (below), the host adapter, `useCanvasGraph`, `CanvasBackdrop`, `canvasNodeTypes` / `canvasEdgeTypes`, the components (`ModelNode`, `FkEdge`, `AnnotationNode`, `AnnotationEdge`, `DetailPanel`, `Legend`, `KeyBadge`, `KeyBadgeGroup`, `DataTypeSelect`, `ColumnRowEditor`), the canvas hooks and the pure helpers. |
|
|
13
13
|
| `@erd-studio/renderer/store` | The canvas store on its own, with no React Flow runtime: `createCanvasSlice`, `createCanvasStore`, `CanvasStoreProvider`, `useEditorStore`, `useEditorStoreApi` and their types. |
|
|
14
14
|
| `@erd-studio/renderer/sizing` | Model node size estimation (`NODE_WIDTH`, `estimateNodeWidth`, `estimateNodeHeight`, `countVisibleColumnRows`, `resolveNodeDimensions`), for layout code that needs node sizes before React Flow has measured them. No components. |
|
|
@@ -49,6 +49,24 @@ The ref exposes `resetLayout()`, which puts every node back at its position in t
|
|
|
49
49
|
|
|
50
50
|
Each `ErdCanvas` has its own store, so several can share a page.
|
|
51
51
|
|
|
52
|
+
## Exporting a diagram
|
|
53
|
+
|
|
54
|
+
The `.` entry re-exports `@erd-studio/core`'s one-way exporters, so a page that shows a diagram with `ErdCanvas` can offer it as text without depending on core itself: `toMermaid`, `toDbml`, `exportDiagram`, `diagramExportFileName`, `DIAGRAM_EXPORT_FORMATS`, `DIAGRAM_EXPORT_FILE_EXTENSIONS` and the `DiagramExportFormat` type. They are pure functions with no React or DOM use; the same `DisplayDomain` gives the same text everywhere ERD Studio exports it.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { exportDiagram, diagramExportFileName, type DisplayDomain } from '@erd-studio/renderer';
|
|
58
|
+
|
|
59
|
+
function downloadDbml(domain: DisplayDomain) {
|
|
60
|
+
const text = exportDiagram(domain, 'dbml'); // or 'mermaid'
|
|
61
|
+
const url = URL.createObjectURL(new Blob([text], { type: 'text/plain' }));
|
|
62
|
+
const link = Object.assign(document.createElement('a'), { href: url, download: diagramExportFileName(domain, 'dbml') });
|
|
63
|
+
link.click();
|
|
64
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
See the [`@erd-studio/core` README](https://github.com/liam-machine/erd-studio/blob/main/packages/core/README.md#exporting-a-diagram) for what each format carries.
|
|
69
|
+
|
|
52
70
|
## Editor: store and host
|
|
53
71
|
|
|
54
72
|
The canvas components read their state from the store supplied by the nearest `CanvasStoreProvider`, and post the edits a user makes on the canvas (renaming a column, moving an annotation, changing a cardinality, …) as `CanvasEditMessage`s to the `CanvasHost` supplied by `CanvasEnvironmentProvider`.
|
|
@@ -66,6 +66,41 @@ function useIsViewer() {
|
|
|
66
66
|
// src/canvas/useCanvasGraph.ts
|
|
67
67
|
import { useCallback as useCallback2, useEffect, useRef } from "react";
|
|
68
68
|
|
|
69
|
+
// src/lib/relationshipDisplayKey.ts
|
|
70
|
+
function columnPairs(rel) {
|
|
71
|
+
return rel.pairs && rel.pairs.length > 0 ? rel.pairs.map((p) => ({ fromColumn: p.fromColumn, toColumn: p.toColumn })) : [{ fromColumn: rel.fromColumn, toColumn: rel.toColumn }];
|
|
72
|
+
}
|
|
73
|
+
function relationshipDisplayKey(rel) {
|
|
74
|
+
const pairs = columnPairs(rel);
|
|
75
|
+
return `${rel.fromModel}|${pairs.map((p) => p.fromColumn).join("+")}|${rel.toModel}|${pairs.map((p) => p.toColumn).join("+")}`;
|
|
76
|
+
}
|
|
77
|
+
function relationshipEdgeId(rel) {
|
|
78
|
+
const pairs = columnPairs(rel);
|
|
79
|
+
return `fk-${rel.fromModel}-${pairs.map((p) => p.fromColumn).join("+")}-${rel.toModel}-${pairs.map((p) => p.toColumn).join("+")}`;
|
|
80
|
+
}
|
|
81
|
+
function foldComposites(rels) {
|
|
82
|
+
const groups = /* @__PURE__ */ new Map();
|
|
83
|
+
const idOf = (r) => [r.fromModel, r.toModel, r.compositeKey].map((s) => s.toLowerCase()).join("\0");
|
|
84
|
+
for (const r of rels) {
|
|
85
|
+
if (!r.compositeKey) continue;
|
|
86
|
+
groups.set(idOf(r), [...groups.get(idOf(r)) ?? [], r]);
|
|
87
|
+
}
|
|
88
|
+
const out = [];
|
|
89
|
+
const placed = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const r of rels) {
|
|
91
|
+
if (!r.compositeKey) {
|
|
92
|
+
out.push(r);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const id = idOf(r);
|
|
96
|
+
if (placed.has(id)) continue;
|
|
97
|
+
placed.add(id);
|
|
98
|
+
const members = groups.get(id);
|
|
99
|
+
out.push(members.length < 2 ? r : { ...members[0], pairs: members.map((m) => ({ fromColumn: m.fromColumn, toColumn: m.toColumn })) });
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
69
104
|
// src/lib/modelLabels.ts
|
|
70
105
|
function computeModelLabels(models) {
|
|
71
106
|
const baseOf = (m) => m.alias?.trim() || m.name;
|
|
@@ -102,7 +137,8 @@ function mapColumns(model) {
|
|
|
102
137
|
isNaturalKey: col.isNaturalKey,
|
|
103
138
|
...col.scdType != null ? { scdType: col.scdType } : {},
|
|
104
139
|
...col.additiveType ? { additiveType: col.additiveType } : {},
|
|
105
|
-
...hasMeta(col.meta) ? { meta: col.meta } : {}
|
|
140
|
+
...hasMeta(col.meta) ? { meta: col.meta } : {},
|
|
141
|
+
...col.dbtKey ? { dbtKey: col.dbtKey } : {}
|
|
106
142
|
}));
|
|
107
143
|
return mapped;
|
|
108
144
|
}
|
|
@@ -218,20 +254,16 @@ function transformDomain(domain, options) {
|
|
|
218
254
|
const relDiscrepancyMap = /* @__PURE__ */ new Map();
|
|
219
255
|
if (options?.discrepancyReport) {
|
|
220
256
|
for (const rd of options.discrepancyReport.relationships) {
|
|
221
|
-
if (rd.status !== "matched")
|
|
222
|
-
const key = `${rd.fromModel}|${rd.fromColumn}|${rd.toModel}|${rd.toColumn}`;
|
|
223
|
-
relDiscrepancyMap.set(key, rd.status);
|
|
224
|
-
}
|
|
257
|
+
if (rd.status !== "matched") relDiscrepancyMap.set(relationshipDisplayKey(rd), rd.status);
|
|
225
258
|
}
|
|
226
259
|
}
|
|
227
260
|
const allNodeNames = new Set(positionMap.keys());
|
|
228
|
-
const edges = relationships.filter((rel) => allNodeNames.has(rel.fromModel) && allNodeNames.has(rel.toModel)).map((rel) => {
|
|
261
|
+
const edges = foldComposites(relationships).filter((rel) => allNodeNames.has(rel.fromModel) && allNodeNames.has(rel.toModel)).map((rel) => {
|
|
229
262
|
const isSelfLoop = rel.fromModel === rel.toModel;
|
|
230
263
|
const { sourceSide, targetSide } = isSelfLoop ? { sourceSide: "top", targetSide: "right" } : pickHandleSides(rectOf(rel.fromModel), rectOf(rel.toModel));
|
|
231
|
-
const
|
|
232
|
-
const discStatus = relDiscrepancyMap.get(relKey);
|
|
264
|
+
const discStatus = relDiscrepancyMap.get(relationshipDisplayKey(rel));
|
|
233
265
|
return {
|
|
234
|
-
id:
|
|
266
|
+
id: relationshipEdgeId(rel),
|
|
235
267
|
type: "fk",
|
|
236
268
|
source: rel.fromModel,
|
|
237
269
|
target: rel.toModel,
|
|
@@ -243,6 +275,8 @@ function transformDomain(domain, options) {
|
|
|
243
275
|
toModel: rel.toModel,
|
|
244
276
|
toColumn: rel.toColumn,
|
|
245
277
|
cardinality: rel.cardinality,
|
|
278
|
+
...rel.role ? { role: rel.role } : {},
|
|
279
|
+
...rel.pairs ? { pairs: rel.pairs, compositeKey: rel.compositeKey } : {},
|
|
246
280
|
stage,
|
|
247
281
|
...readOnly ? { readOnly: true } : {},
|
|
248
282
|
...discStatus ? { discrepancyStatus: discStatus } : {},
|
|
@@ -256,8 +290,10 @@ function transformDomain(domain, options) {
|
|
|
256
290
|
if (!positionMap.has(rd.fromModel) || !positionMap.has(rd.toModel)) continue;
|
|
257
291
|
const isSelfLoop = rd.fromModel === rd.toModel;
|
|
258
292
|
const { sourceSide, targetSide } = isSelfLoop ? { sourceSide: "top", targetSide: "right" } : pickHandleSides(rectOf(rd.fromModel), rectOf(rd.toModel));
|
|
293
|
+
const pairs = columnPairs(rd);
|
|
294
|
+
const composite = pairs.length > 1 ? { pairs, ...rd.compositeKey ? { compositeKey: rd.compositeKey } : {} } : {};
|
|
259
295
|
edges.push({
|
|
260
|
-
id: `ghost
|
|
296
|
+
id: `ghost-${relationshipEdgeId({ ...rd, pairs })}`,
|
|
261
297
|
type: "fk",
|
|
262
298
|
source: rd.fromModel,
|
|
263
299
|
target: rd.toModel,
|
|
@@ -269,6 +305,7 @@ function transformDomain(domain, options) {
|
|
|
269
305
|
toModel: rd.toModel,
|
|
270
306
|
toColumn: rd.toColumn,
|
|
271
307
|
cardinality: rd.sourceCardinality ?? rd.targetCardinality ?? "many-to-one",
|
|
308
|
+
...composite,
|
|
272
309
|
stage,
|
|
273
310
|
discrepancyStatus: "missing",
|
|
274
311
|
...isSelfLoop ? { isSelfLoop: true } : {}
|
|
@@ -277,6 +314,16 @@ function transformDomain(domain, options) {
|
|
|
277
314
|
}
|
|
278
315
|
}
|
|
279
316
|
}
|
|
317
|
+
const loopsByNode = /* @__PURE__ */ new Map();
|
|
318
|
+
for (const edge of edges) {
|
|
319
|
+
if (edge.type !== "fk" || !edge.data?.isSelfLoop) continue;
|
|
320
|
+
loopsByNode.set(edge.source, [...loopsByNode.get(edge.source) ?? [], edge]);
|
|
321
|
+
}
|
|
322
|
+
for (const loops of loopsByNode.values()) {
|
|
323
|
+
[...loops].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).forEach((edge, loopIndex) => {
|
|
324
|
+
edge.data = { ...edge.data, loopIndex };
|
|
325
|
+
});
|
|
326
|
+
}
|
|
280
327
|
const annotations = domain.viewConfig.annotations ?? [];
|
|
281
328
|
for (const ann of annotations) {
|
|
282
329
|
const annNodeId = `annotation-${ann.id}`;
|
|
@@ -499,8 +546,10 @@ function useCanvasGraph({
|
|
|
499
546
|
if (edge.type !== "fk") return;
|
|
500
547
|
if (edge.data) {
|
|
501
548
|
const cols = /* @__PURE__ */ new Set();
|
|
502
|
-
|
|
503
|
-
|
|
549
|
+
for (const pair of columnPairs(edge.data)) {
|
|
550
|
+
cols.add(`${edge.data.fromModel}:${pair.fromColumn}`);
|
|
551
|
+
cols.add(`${edge.data.toModel}:${pair.toColumn}`);
|
|
552
|
+
}
|
|
504
553
|
setHighlightedColumns(cols);
|
|
505
554
|
selectNode(null);
|
|
506
555
|
setDetailPanelOpen(false);
|
|
@@ -662,6 +711,19 @@ function useLongPressDrag(options = {}) {
|
|
|
662
711
|
};
|
|
663
712
|
}
|
|
664
713
|
|
|
714
|
+
// src/lib/dbtKeyTitle.ts
|
|
715
|
+
function dbtKeyTitle(hint) {
|
|
716
|
+
switch (hint.because) {
|
|
717
|
+
case "unique-test":
|
|
718
|
+
case "unique-combination":
|
|
719
|
+
return "dbt: tested as unique";
|
|
720
|
+
case "part-of-unique-combination":
|
|
721
|
+
return "dbt: part of a unique combination";
|
|
722
|
+
case "relationships-test":
|
|
723
|
+
return "dbt: has a relationships test";
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
665
727
|
// src/hooks/useColumnReorder.ts
|
|
666
728
|
import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
667
729
|
function useColumnReorder({
|
|
@@ -1249,9 +1311,12 @@ var DISCREPANCY_PHRASE = {
|
|
|
1249
1311
|
"cardinality-mismatch": "cardinality differs between stages"
|
|
1250
1312
|
};
|
|
1251
1313
|
function edgeHoverText(edge) {
|
|
1314
|
+
const composite = edge.pairs && edge.pairs.length > 1;
|
|
1315
|
+
const ends = composite ? `${edge.fromModel} (${edge.pairs.map((p) => p.fromColumn).join(", ")}) \u2192 ${edge.toModel} (${edge.pairs.map((p) => p.toColumn).join(", ")})` : `${edge.fromModel}.${edge.fromColumn} \u2192 ${edge.toModel}.${edge.toColumn}`;
|
|
1252
1316
|
return [
|
|
1253
|
-
|
|
1317
|
+
ends,
|
|
1254
1318
|
edge.cardinality.replace(/-/g, " "),
|
|
1319
|
+
edge.role ? `role: ${edge.role}` : "",
|
|
1255
1320
|
edge.discrepancyStatus ? DISCREPANCY_PHRASE[edge.discrepancyStatus] : ""
|
|
1256
1321
|
].filter(Boolean).join(" \xB7 ");
|
|
1257
1322
|
}
|
|
@@ -1454,21 +1519,21 @@ function ColumnRow({ column, modelName, readOnly, existingColumnNames, discrepan
|
|
|
1454
1519
|
if (columnRow && modelNode) {
|
|
1455
1520
|
const targetColumnName = columnRow.dataset.columnName;
|
|
1456
1521
|
const targetModelName = modelNode.dataset.modelName;
|
|
1457
|
-
if (targetColumnName && targetModelName
|
|
1458
|
-
|
|
1459
|
-
new CustomEvent("column-relationship-drop"
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1522
|
+
if (targetColumnName && targetModelName) {
|
|
1523
|
+
if (targetModelName === modelName && targetColumnName.toLowerCase() === column.name.toLowerCase()) {
|
|
1524
|
+
window.dispatchEvent(new CustomEvent("column-relationship-self-drop"));
|
|
1525
|
+
} else {
|
|
1526
|
+
window.dispatchEvent(
|
|
1527
|
+
new CustomEvent("column-relationship-drop", {
|
|
1528
|
+
detail: {
|
|
1529
|
+
fromModel: modelName,
|
|
1530
|
+
fromColumn: column.name,
|
|
1531
|
+
toModel: targetModelName,
|
|
1532
|
+
toColumn: targetColumnName
|
|
1533
|
+
}
|
|
1534
|
+
})
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1472
1537
|
}
|
|
1473
1538
|
}
|
|
1474
1539
|
endDrag();
|
|
@@ -1567,7 +1632,8 @@ function ColumnRow({ column, modelName, readOnly, existingColumnNames, discrepan
|
|
|
1567
1632
|
"span",
|
|
1568
1633
|
{
|
|
1569
1634
|
className: `model-node__col-name${!readOnly ? " model-node__col-name--editable" : ""}`,
|
|
1570
|
-
title: column.name
|
|
1635
|
+
title: !viewer && column.dbtKey ? `${column.name}
|
|
1636
|
+
${dbtKeyTitle(column.dbtKey)}` : column.name,
|
|
1571
1637
|
onDoubleClick: !readOnly ? handleDoubleClickName : void 0,
|
|
1572
1638
|
children: column.name
|
|
1573
1639
|
}
|
|
@@ -1672,6 +1738,9 @@ function describeLoadError(error) {
|
|
|
1672
1738
|
if (error.kind === "read") {
|
|
1673
1739
|
return "ERD Studio can't read this file.";
|
|
1674
1740
|
}
|
|
1741
|
+
if (error.mergeConflict) {
|
|
1742
|
+
return error.line !== void 0 ? `Unresolved git merge conflict on line ${error.line} \u2014 ERD Studio can't read this file.` : "Unresolved git merge conflict \u2014 ERD Studio can't read this file.";
|
|
1743
|
+
}
|
|
1675
1744
|
return error.line !== void 0 ? `YAML error on line ${error.line} \u2014 ERD Studio can't read this file.` : "YAML error \u2014 ERD Studio can't read this file.";
|
|
1676
1745
|
}
|
|
1677
1746
|
function ModelNodeComponent({ data, selected }) {
|
|
@@ -2017,6 +2086,7 @@ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-run
|
|
|
2017
2086
|
var PATH_GAP = 20;
|
|
2018
2087
|
var LABEL_OFFSET = 8;
|
|
2019
2088
|
var SELF_LOOP_RADIUS = 55;
|
|
2089
|
+
var SELF_LOOP_STEP = 18;
|
|
2020
2090
|
var NO_SIBLINGS = Object.freeze([]);
|
|
2021
2091
|
function offsetPoint(x, y, position, distance) {
|
|
2022
2092
|
switch (position) {
|
|
@@ -2089,7 +2159,8 @@ function FkEdgeComponent({
|
|
|
2089
2159
|
[host, data]
|
|
2090
2160
|
);
|
|
2091
2161
|
if (!data) return null;
|
|
2092
|
-
const { cardinality, stage, discrepancyStatus, dimmed, readOnly, isSelfLoop, fromColumn, toColumn } = data;
|
|
2162
|
+
const { cardinality, role, stage, discrepancyStatus, dimmed, readOnly, isSelfLoop, fromColumn, toColumn } = data;
|
|
2163
|
+
const loopRadius = SELF_LOOP_RADIUS + SELF_LOOP_STEP * (data.loopIndex ?? 0);
|
|
2093
2164
|
let adjustedSourceX = sourceX;
|
|
2094
2165
|
let adjustedSourceY = sourceY;
|
|
2095
2166
|
let adjustedTargetX = targetX;
|
|
@@ -2122,7 +2193,7 @@ function FkEdgeComponent({
|
|
|
2122
2193
|
}
|
|
2123
2194
|
const src = offsetPoint(adjustedSourceX, adjustedSourceY, sourcePosition, PATH_GAP);
|
|
2124
2195
|
const tgt = offsetPoint(adjustedTargetX, adjustedTargetY, targetPosition, PATH_GAP);
|
|
2125
|
-
const edgePath = isSelfLoop ? `M ${src.x},${src.y} C ${src.x +
|
|
2196
|
+
const edgePath = isSelfLoop ? `M ${src.x},${src.y} C ${src.x + loopRadius},${src.y - loopRadius} ${tgt.x + loopRadius},${tgt.y - loopRadius} ${tgt.x},${tgt.y}` : getSmoothStepPath({
|
|
2126
2197
|
sourceX: src.x,
|
|
2127
2198
|
sourceY: src.y,
|
|
2128
2199
|
targetX: tgt.x,
|
|
@@ -2138,8 +2209,11 @@ function FkEdgeComponent({
|
|
|
2138
2209
|
toModel,
|
|
2139
2210
|
toColumn,
|
|
2140
2211
|
cardinality,
|
|
2141
|
-
|
|
2212
|
+
role,
|
|
2213
|
+
discrepancyStatus,
|
|
2214
|
+
pairs: data.pairs
|
|
2142
2215
|
});
|
|
2216
|
+
const compositeSize = data.pairs && data.pairs.length > 1 ? data.pairs.length : 0;
|
|
2143
2217
|
let sourceLabel;
|
|
2144
2218
|
let targetLabel;
|
|
2145
2219
|
switch (cardinality) {
|
|
@@ -2168,8 +2242,8 @@ function FkEdgeComponent({
|
|
|
2168
2242
|
const labelColorClass = discrepancyStatus ? `fk-edge__label--discrepancy-${discrepancyStatus}` : `fk-edge__label--${stage ?? "logical"}`;
|
|
2169
2243
|
const srcLabel = offsetPoint(adjustedSourceX, adjustedSourceY, sourcePosition, LABEL_OFFSET);
|
|
2170
2244
|
const tgtLabel = offsetPoint(adjustedTargetX, adjustedTargetY, targetPosition, LABEL_OFFSET);
|
|
2171
|
-
const midX = isSelfLoop ? (src.x + tgt.x) / 2 +
|
|
2172
|
-
const midY = isSelfLoop ? (src.y + tgt.y) / 2 -
|
|
2245
|
+
const midX = isSelfLoop ? (src.x + tgt.x) / 2 + loopRadius * 0.75 : (adjustedSourceX + adjustedTargetX) / 2;
|
|
2246
|
+
const midY = isSelfLoop ? (src.y + tgt.y) / 2 - loopRadius * 0.75 : (adjustedSourceY + adjustedTargetY) / 2;
|
|
2173
2247
|
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
2174
2248
|
/* @__PURE__ */ jsx8(
|
|
2175
2249
|
"path",
|
|
@@ -2211,6 +2285,30 @@ function FkEdgeComponent({
|
|
|
2211
2285
|
children: targetLabel
|
|
2212
2286
|
}
|
|
2213
2287
|
),
|
|
2288
|
+
role && /* @__PURE__ */ jsx8(
|
|
2289
|
+
"span",
|
|
2290
|
+
{
|
|
2291
|
+
className: `fk-edge__role ${labelColorClass}${dimmed ? " fk-edge__label--dimmed" : ""}`,
|
|
2292
|
+
style: {
|
|
2293
|
+
transform: `translate(-50%, calc(-100% - 10px)) translate(${midX}px, ${midY}px)`
|
|
2294
|
+
},
|
|
2295
|
+
children: role
|
|
2296
|
+
}
|
|
2297
|
+
),
|
|
2298
|
+
compositeSize > 0 && /* @__PURE__ */ jsxs6(
|
|
2299
|
+
"span",
|
|
2300
|
+
{
|
|
2301
|
+
className: `fk-edge__composite ${labelColorClass}${dimmed ? " fk-edge__label--dimmed" : ""}`,
|
|
2302
|
+
style: {
|
|
2303
|
+
transform: `translate(-50%, calc(100% + 6px)) translate(${midX}px, ${midY}px)`
|
|
2304
|
+
},
|
|
2305
|
+
title: `Composite foreign key: ${compositeSize} column pairs`,
|
|
2306
|
+
children: [
|
|
2307
|
+
"\u29C9 ",
|
|
2308
|
+
compositeSize
|
|
2309
|
+
]
|
|
2310
|
+
}
|
|
2311
|
+
),
|
|
2214
2312
|
discrepancyStatus === "cardinality-mismatch" && /* @__PURE__ */ jsx8(
|
|
2215
2313
|
"span",
|
|
2216
2314
|
{
|
|
@@ -4538,6 +4636,10 @@ function ColumnEditor({ modelName, columns, readOnly, modelRole }) {
|
|
|
4538
4636
|
|
|
4539
4637
|
// src/components/DetailPanel/DetailPanel.tsx
|
|
4540
4638
|
import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
4639
|
+
function columnsText(rel, end) {
|
|
4640
|
+
const cols = columnPairs(rel).map((p) => end === "from" ? p.fromColumn : p.toColumn);
|
|
4641
|
+
return cols.length === 1 ? cols[0] : `(${cols.join(", ")})`;
|
|
4642
|
+
}
|
|
4541
4643
|
var SOURCE_NAME = {
|
|
4542
4644
|
catalog: "warehouse catalog",
|
|
4543
4645
|
yml: "your dbt .yml",
|
|
@@ -4659,6 +4761,7 @@ function DetailPanel() {
|
|
|
4659
4761
|
handleClose();
|
|
4660
4762
|
}, [selectedNode, host, handleClose]);
|
|
4661
4763
|
const handleDeleteRelationship = useCallback22(
|
|
4764
|
+
// A composite sends its first pair; the host removes the whole group (#133 L2).
|
|
4662
4765
|
(rel) => {
|
|
4663
4766
|
host.postMessage({
|
|
4664
4767
|
type: "removeRelationship",
|
|
@@ -4679,7 +4782,10 @@ function DetailPanel() {
|
|
|
4679
4782
|
fromColumn: rel.fromColumn,
|
|
4680
4783
|
toModel: rel.toModel,
|
|
4681
4784
|
toColumn: rel.toColumn,
|
|
4682
|
-
cardinality: rel.cardinality
|
|
4785
|
+
cardinality: rel.cardinality,
|
|
4786
|
+
// Carried so Edit opens with it — the dialog saves what it shows.
|
|
4787
|
+
...rel.role ? { role: rel.role } : {},
|
|
4788
|
+
...rel.pairs ? { pairs: rel.pairs, ...rel.compositeKey ? { compositeKey: rel.compositeKey } : {} } : {}
|
|
4683
4789
|
};
|
|
4684
4790
|
openEdgeContextMenu(x, y, edgeData);
|
|
4685
4791
|
},
|
|
@@ -4693,8 +4799,9 @@ function DetailPanel() {
|
|
|
4693
4799
|
if (!domain || !selectedNode) {
|
|
4694
4800
|
return { outgoing: [], incoming: [] };
|
|
4695
4801
|
}
|
|
4696
|
-
const
|
|
4697
|
-
const
|
|
4802
|
+
const rels = foldComposites(domain.relationships);
|
|
4803
|
+
const outgoing2 = rels.filter((r) => r.fromModel === selectedNode);
|
|
4804
|
+
const incoming2 = rels.filter((r) => r.toModel === selectedNode && r.fromModel !== selectedNode);
|
|
4698
4805
|
return { outgoing: outgoing2, incoming: incoming2 };
|
|
4699
4806
|
}, [domain, selectedNode]);
|
|
4700
4807
|
const isReadOnly = domain?.readOnly ?? false;
|
|
@@ -4862,14 +4969,14 @@ function DetailPanel() {
|
|
|
4862
4969
|
},
|
|
4863
4970
|
title: viewer ? void 0 : "Click to edit cardinality",
|
|
4864
4971
|
children: [
|
|
4865
|
-
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-direction", title: "Outgoing FK", children: "\u2192" }),
|
|
4972
|
+
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-direction", title: rel.toModel === rel.fromModel ? "Self-reference" : "Outgoing FK", children: rel.toModel === rel.fromModel ? "\u21BB" : "\u2192" }),
|
|
4866
4973
|
/* @__PURE__ */ jsxs19("span", { className: "detail-panel__rel-columns", children: [
|
|
4867
|
-
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-local", title: rel
|
|
4974
|
+
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-local", title: columnsText(rel, "from"), children: columnsText(rel, "from") }),
|
|
4868
4975
|
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-arrow", children: "\u2192" }),
|
|
4869
|
-
/* @__PURE__ */ jsxs19("span", { className: "detail-panel__rel-target", title: `${rel.toModel}.${rel
|
|
4976
|
+
/* @__PURE__ */ jsxs19("span", { className: "detail-panel__rel-target", title: `${rel.toModel}.${columnsText(rel, "to")}`, children: [
|
|
4870
4977
|
rel.toModel,
|
|
4871
4978
|
".",
|
|
4872
|
-
rel
|
|
4979
|
+
columnsText(rel, "to")
|
|
4873
4980
|
] })
|
|
4874
4981
|
] }),
|
|
4875
4982
|
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-cardinality", children: rel.cardinality }),
|
|
@@ -4888,7 +4995,7 @@ function DetailPanel() {
|
|
|
4888
4995
|
)
|
|
4889
4996
|
]
|
|
4890
4997
|
},
|
|
4891
|
-
`out-${rel
|
|
4998
|
+
`out-${relationshipEdgeId(rel)}`
|
|
4892
4999
|
)),
|
|
4893
5000
|
incoming.map((rel) => /* @__PURE__ */ jsxs19(
|
|
4894
5001
|
"div",
|
|
@@ -4908,13 +5015,13 @@ function DetailPanel() {
|
|
|
4908
5015
|
children: [
|
|
4909
5016
|
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-direction", title: "Incoming FK", children: "\u2190" }),
|
|
4910
5017
|
/* @__PURE__ */ jsxs19("span", { className: "detail-panel__rel-columns", children: [
|
|
4911
|
-
/* @__PURE__ */ jsxs19("span", { className: "detail-panel__rel-target", title: `${rel.fromModel}.${rel
|
|
5018
|
+
/* @__PURE__ */ jsxs19("span", { className: "detail-panel__rel-target", title: `${rel.fromModel}.${columnsText(rel, "from")}`, children: [
|
|
4912
5019
|
rel.fromModel,
|
|
4913
5020
|
".",
|
|
4914
|
-
rel
|
|
5021
|
+
columnsText(rel, "from")
|
|
4915
5022
|
] }),
|
|
4916
5023
|
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-arrow", children: "\u2192" }),
|
|
4917
|
-
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-local", title: rel
|
|
5024
|
+
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-local", title: columnsText(rel, "to"), children: columnsText(rel, "to") })
|
|
4918
5025
|
] }),
|
|
4919
5026
|
/* @__PURE__ */ jsx22("span", { className: "detail-panel__rel-cardinality", children: rel.cardinality }),
|
|
4920
5027
|
!isReadOnly && /* @__PURE__ */ jsx22(
|
|
@@ -4932,7 +5039,7 @@ function DetailPanel() {
|
|
|
4932
5039
|
)
|
|
4933
5040
|
]
|
|
4934
5041
|
},
|
|
4935
|
-
`in-${rel
|
|
5042
|
+
`in-${relationshipEdgeId(rel)}`
|
|
4936
5043
|
))
|
|
4937
5044
|
] })
|
|
4938
5045
|
] })
|
|
@@ -5379,12 +5486,26 @@ var ErdCanvas = forwardRef(function ErdCanvas2(props, ref) {
|
|
|
5379
5486
|
return /* @__PURE__ */ jsx24(CanvasStoreProvider, { store, children: /* @__PURE__ */ jsx24(CanvasEnvironmentProvider, { host: NOOP_CANVAS_HOST, viewer: true, children: /* @__PURE__ */ jsx24(ReactFlowProvider, { children: /* @__PURE__ */ jsx24(ErdCanvasInner, { ...props, domain: renderable, handleRef: ref }) }) }) });
|
|
5380
5487
|
});
|
|
5381
5488
|
|
|
5489
|
+
// src/index.ts
|
|
5490
|
+
import {
|
|
5491
|
+
toMermaid,
|
|
5492
|
+
toDbml,
|
|
5493
|
+
exportDiagram,
|
|
5494
|
+
diagramExportFileName,
|
|
5495
|
+
DIAGRAM_EXPORT_FORMATS,
|
|
5496
|
+
DIAGRAM_EXPORT_FILE_EXTENSIONS
|
|
5497
|
+
} from "@erd-studio/core";
|
|
5498
|
+
|
|
5382
5499
|
export {
|
|
5383
5500
|
NOOP_CANVAS_HOST,
|
|
5384
5501
|
CanvasEnvironmentProvider,
|
|
5385
5502
|
useCanvasHost,
|
|
5386
5503
|
useSend,
|
|
5387
5504
|
useIsViewer,
|
|
5505
|
+
columnPairs,
|
|
5506
|
+
relationshipDisplayKey,
|
|
5507
|
+
relationshipEdgeId,
|
|
5508
|
+
foldComposites,
|
|
5388
5509
|
computeModelLabels,
|
|
5389
5510
|
matchesModelSearch,
|
|
5390
5511
|
pickHandleSides,
|
|
@@ -5425,5 +5546,11 @@ export {
|
|
|
5425
5546
|
ColumnRowEditor,
|
|
5426
5547
|
DetailPanel,
|
|
5427
5548
|
Legend,
|
|
5428
|
-
ErdCanvas
|
|
5549
|
+
ErdCanvas,
|
|
5550
|
+
toMermaid,
|
|
5551
|
+
toDbml,
|
|
5552
|
+
exportDiagram,
|
|
5553
|
+
diagramExportFileName,
|
|
5554
|
+
DIAGRAM_EXPORT_FORMATS,
|
|
5555
|
+
DIAGRAM_EXPORT_FILE_EXTENSIONS
|
|
5429
5556
|
};
|
package/dist/editor.js
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
CanvasBackdrop,
|
|
9
9
|
CanvasEnvironmentProvider,
|
|
10
10
|
ColumnRowEditor,
|
|
11
|
+
DIAGRAM_EXPORT_FILE_EXTENSIONS,
|
|
12
|
+
DIAGRAM_EXPORT_FORMATS,
|
|
11
13
|
DISCREPANCY_PHRASE,
|
|
12
14
|
DataTypeSelect,
|
|
13
15
|
DetailPanel,
|
|
@@ -28,15 +30,23 @@ import {
|
|
|
28
30
|
applyNodeOverlays,
|
|
29
31
|
canvasEdgeTypes,
|
|
30
32
|
canvasNodeTypes,
|
|
33
|
+
columnPairs,
|
|
31
34
|
computeModelLabels,
|
|
35
|
+
diagramExportFileName,
|
|
32
36
|
edgeHoverText,
|
|
37
|
+
exportDiagram,
|
|
38
|
+
foldComposites,
|
|
33
39
|
getDataTypeColor,
|
|
34
40
|
matchesModelSearch,
|
|
35
41
|
nodeRect,
|
|
36
42
|
pickHandleSides,
|
|
43
|
+
relationshipDisplayKey,
|
|
44
|
+
relationshipEdgeId,
|
|
37
45
|
repickHandleSides,
|
|
38
46
|
stageNodeColor,
|
|
39
47
|
swapCardinality,
|
|
48
|
+
toDbml,
|
|
49
|
+
toMermaid,
|
|
40
50
|
transformDomain,
|
|
41
51
|
useCanvasGraph,
|
|
42
52
|
useCanvasHost,
|
|
@@ -45,7 +55,7 @@ import {
|
|
|
45
55
|
useIsViewer,
|
|
46
56
|
useLongPressDrag,
|
|
47
57
|
useSend
|
|
48
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-H2ACJRPZ.js";
|
|
49
59
|
import "./chunk-QZGU5NZH.js";
|
|
50
60
|
import "./chunk-6XQXDZ4R.js";
|
|
51
61
|
import {
|
|
@@ -77,6 +87,8 @@ export {
|
|
|
77
87
|
CanvasEnvironmentProvider,
|
|
78
88
|
CanvasStoreProvider,
|
|
79
89
|
ColumnRowEditor,
|
|
90
|
+
DIAGRAM_EXPORT_FILE_EXTENSIONS,
|
|
91
|
+
DIAGRAM_EXPORT_FORMATS,
|
|
80
92
|
DISCREPANCY_PHRASE,
|
|
81
93
|
DataTypeSelect,
|
|
82
94
|
DetailPanel,
|
|
@@ -99,21 +111,29 @@ export {
|
|
|
99
111
|
applyNodeOverlays,
|
|
100
112
|
canvasEdgeTypes,
|
|
101
113
|
canvasNodeTypes,
|
|
114
|
+
columnPairs,
|
|
102
115
|
computeModelLabels,
|
|
103
116
|
countVisibleColumnRows,
|
|
104
117
|
createCanvasSlice,
|
|
105
118
|
createCanvasStore,
|
|
119
|
+
diagramExportFileName,
|
|
106
120
|
edgeHoverText,
|
|
107
121
|
estimateNodeHeight,
|
|
108
122
|
estimateNodeWidth,
|
|
123
|
+
exportDiagram,
|
|
124
|
+
foldComposites,
|
|
109
125
|
getDataTypeColor,
|
|
110
126
|
matchesModelSearch,
|
|
111
127
|
nodeRect,
|
|
112
128
|
pickHandleSides,
|
|
129
|
+
relationshipDisplayKey,
|
|
130
|
+
relationshipEdgeId,
|
|
113
131
|
repickHandleSides,
|
|
114
132
|
resolveNodeDimensions,
|
|
115
133
|
stageNodeColor,
|
|
116
134
|
swapCardinality,
|
|
135
|
+
toDbml,
|
|
136
|
+
toMermaid,
|
|
117
137
|
transformDomain,
|
|
118
138
|
useCanvasGraph,
|
|
119
139
|
useCanvasHost,
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DIAGRAM_EXPORT_FILE_EXTENSIONS,
|
|
3
|
+
DIAGRAM_EXPORT_FORMATS,
|
|
2
4
|
ErdCanvas,
|
|
5
|
+
diagramExportFileName,
|
|
6
|
+
exportDiagram,
|
|
3
7
|
nodeRect,
|
|
4
8
|
pickHandleSides,
|
|
5
9
|
repickHandleSides,
|
|
10
|
+
toDbml,
|
|
11
|
+
toMermaid,
|
|
6
12
|
transformDomain
|
|
7
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-H2ACJRPZ.js";
|
|
8
14
|
import "./chunk-DUQ2TTQH.js";
|
|
9
15
|
import "./chunk-WOTSQZCQ.js";
|
|
10
16
|
export {
|
|
17
|
+
DIAGRAM_EXPORT_FILE_EXTENSIONS,
|
|
18
|
+
DIAGRAM_EXPORT_FORMATS,
|
|
11
19
|
ErdCanvas,
|
|
20
|
+
diagramExportFileName,
|
|
21
|
+
exportDiagram,
|
|
12
22
|
nodeRect,
|
|
13
23
|
pickHandleSides,
|
|
14
24
|
repickHandleSides,
|
|
25
|
+
toDbml,
|
|
26
|
+
toMermaid,
|
|
15
27
|
transformDomain
|
|
16
28
|
};
|
package/dist/styles.css
CHANGED
|
@@ -818,6 +818,29 @@
|
|
|
818
818
|
.fk-edge__label--discrepancy-cardinality-mismatch {
|
|
819
819
|
color: var(--discrepancy-mismatch);
|
|
820
820
|
}
|
|
821
|
+
.fk-edge__role {
|
|
822
|
+
position: absolute;
|
|
823
|
+
pointer-events: none;
|
|
824
|
+
padding: 1px 6px;
|
|
825
|
+
border-radius: 8px;
|
|
826
|
+
background: var(--panel-bg);
|
|
827
|
+
font-size: 11px;
|
|
828
|
+
font-weight: 600;
|
|
829
|
+
line-height: 1.4;
|
|
830
|
+
white-space: nowrap;
|
|
831
|
+
}
|
|
832
|
+
.fk-edge__composite {
|
|
833
|
+
position: absolute;
|
|
834
|
+
pointer-events: none;
|
|
835
|
+
padding: 0 5px;
|
|
836
|
+
border: 1px solid currentColor;
|
|
837
|
+
border-radius: 8px;
|
|
838
|
+
background: var(--panel-bg);
|
|
839
|
+
font-size: 10px;
|
|
840
|
+
font-weight: 600;
|
|
841
|
+
line-height: 1.4;
|
|
842
|
+
white-space: nowrap;
|
|
843
|
+
}
|
|
821
844
|
.fk-edge__mismatch-badge {
|
|
822
845
|
position: absolute;
|
|
823
846
|
display: flex;
|
package/dist/types/editor.d.ts
CHANGED
|
@@ -24,5 +24,6 @@ export * from './lib/stageColors.js';
|
|
|
24
24
|
export * from './lib/dataTypeColors.js';
|
|
25
25
|
export * from './lib/badgeLabels.js';
|
|
26
26
|
export { swapCardinality } from './lib/cardinalityUtils.js';
|
|
27
|
+
export { columnPairs, foldComposites, relationshipDisplayKey, relationshipEdgeId } from './lib/relationshipDisplayKey.js';
|
|
27
28
|
export { computeModelLabels, matchesModelSearch, type LabelledModel } from './lib/modelLabels.js';
|
|
28
29
|
export { ANNOTATION_COLORS } from './lib/annotationColors.js';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { ErdCanvas, type ErdCanvasProps, type ErdCanvasHandle } from './ErdCanva
|
|
|
2
2
|
export { transformDomain, pickHandleSides, type TransformResult, type TransformOptions, type NodeRect, type NodeDimensions, } from './lib/graphTransformer.js';
|
|
3
3
|
export { repickHandleSides, nodeRect } from './lib/dragHandleSides.js';
|
|
4
4
|
export type { DisplayDomain, DisplayModel, DisplayColumn, DisplayRelationship, PhysicalColumnSource, PhysicalProvenance, Annotation, AnnotationColor, Cardinality, Layer, ModelRole, NodePosition, Rationale, Stage, ViewConfig, LayerConfig, } from '@erd-studio/core';
|
|
5
|
+
export { toMermaid, toDbml, exportDiagram, diagramExportFileName, DIAGRAM_EXPORT_FORMATS, DIAGRAM_EXPORT_FILE_EXTENSIONS, type DiagramExportFormat, } from '@erd-studio/core';
|
|
5
6
|
export type { ModelFlowNode, ModelNodeData, FkFlowEdge, FkEdgeData, AnnotationFlowNode, AnnotationFlowEdge, ColumnDisplay, } from './types/graph.js';
|
|
@@ -52,7 +52,13 @@ export interface EdgeHoverInput {
|
|
|
52
52
|
toModel: string;
|
|
53
53
|
toColumn: string;
|
|
54
54
|
cardinality: string;
|
|
55
|
+
role?: string;
|
|
55
56
|
discrepancyStatus?: 'extra' | 'missing' | 'cardinality-mismatch';
|
|
57
|
+
/** A composite foreign key's column pairs (#133 L2). */
|
|
58
|
+
pairs?: ReadonlyArray<{
|
|
59
|
+
fromColumn: string;
|
|
60
|
+
toColumn: string;
|
|
61
|
+
}>;
|
|
56
62
|
}
|
|
57
63
|
/**
|
|
58
64
|
* Hover text for a relationship line: which columns it joins, its cardinality,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DbtKeyHint } from '@erd-studio/core';
|
|
2
|
+
/**
|
|
3
|
+
* The line a column's hover text gains for what dbt's tests say about it
|
|
4
|
+
* (#133 L1). It informs a new relationship's direction only, so it is never
|
|
5
|
+
* a badge: the key flags are the user's design decision.
|
|
6
|
+
*/
|
|
7
|
+
export declare function dbtKeyTitle(hint: DbtKeyHint): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One line per relationship, a composite foreign key included (#133 L2).
|
|
3
|
+
*
|
|
4
|
+
* The members of a composite (entries sharing a `compositeKey`) are folded
|
|
5
|
+
* into one relationship carrying all its column pairs: edges attach to node
|
|
6
|
+
* sides, not column rows, so N member lines between two nodes would only
|
|
7
|
+
* crowd the side. `fromColumn` / `toColumn` stay the first pair, which is what
|
|
8
|
+
* a delete or ⇄ sends — the host expands it to the group.
|
|
9
|
+
*/
|
|
10
|
+
import type { ColumnPair } from '@erd-studio/core';
|
|
11
|
+
type Ends = {
|
|
12
|
+
fromModel: string;
|
|
13
|
+
fromColumn: string;
|
|
14
|
+
toModel: string;
|
|
15
|
+
toColumn: string;
|
|
16
|
+
};
|
|
17
|
+
type WithPairs = Ends & {
|
|
18
|
+
pairs?: readonly ColumnPair[];
|
|
19
|
+
};
|
|
20
|
+
/** The column pairs a (possibly folded) relationship joins: its `pairs`, else its one pair. */
|
|
21
|
+
export declare function columnPairs(rel: WithPairs): ColumnPair[];
|
|
22
|
+
/** `from|a+b|to|c+d`: how an edge and its discrepancy entry find each other. */
|
|
23
|
+
export declare function relationshipDisplayKey(rel: WithPairs): string;
|
|
24
|
+
/** The React Flow edge id graphTransformer gives a relationship. */
|
|
25
|
+
export declare function relationshipEdgeId(rel: WithPairs): string;
|
|
26
|
+
/**
|
|
27
|
+
* Fold the members of each composite foreign key into one relationship with
|
|
28
|
+
* `pairs` (two or more), placed where its first member falls; everything else
|
|
29
|
+
* passes through unchanged. Members are grouped by from-model, to-model and
|
|
30
|
+
* `compositeKey`, without case.
|
|
31
|
+
*/
|
|
32
|
+
export declare function foldComposites<T extends Ends & {
|
|
33
|
+
compositeKey?: string;
|
|
34
|
+
}>(rels: readonly T[]): Array<T & {
|
|
35
|
+
pairs?: ColumnPair[];
|
|
36
|
+
}>;
|
|
37
|
+
export {};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* these shapes.
|
|
7
7
|
*/
|
|
8
8
|
import type { Node, Edge } from '@xyflow/react';
|
|
9
|
-
import type { AnnotationColor, Cardinality, Layer, Meta, ModelLoadError, ModelRole, Stage } from '@erd-studio/core';
|
|
9
|
+
import type { AnnotationColor, Cardinality, ColumnPair, DbtKeyHint, Layer, Meta, ModelLoadError, ModelRole, Stage } from '@erd-studio/core';
|
|
10
10
|
import type { LayerConfig } from '@erd-studio/core';
|
|
11
11
|
import type { ModelDiscrepancy } from '@erd-studio/core';
|
|
12
12
|
import type { PhysicalProvenance } from '@erd-studio/core';
|
|
@@ -27,6 +27,8 @@ export interface ColumnDisplay {
|
|
|
27
27
|
additiveType?: 'additive' | 'semi-additive' | 'non-additive';
|
|
28
28
|
/** Structured metadata (`meta:`) — shown in the tooltip. Absent when empty. */
|
|
29
29
|
meta?: Meta;
|
|
30
|
+
/** What dbt's tests say about the column (editable logical canvas only) — one line of its hover text. */
|
|
31
|
+
dbtKey?: DbtKeyHint;
|
|
30
32
|
}
|
|
31
33
|
/** Data payload for a ModelNode React Flow node. */
|
|
32
34
|
export type ModelNodeData = {
|
|
@@ -119,6 +121,15 @@ export type FkEdgeData = {
|
|
|
119
121
|
toModel: string;
|
|
120
122
|
toColumn: string;
|
|
121
123
|
cardinality: Cardinality;
|
|
124
|
+
/** Optional label for the link, e.g. `ship date`, drawn at the edge's midpoint. */
|
|
125
|
+
role?: string;
|
|
126
|
+
/**
|
|
127
|
+
* A composite foreign key's column pairs (two or more; #133 L2). Absent for
|
|
128
|
+
* a single-column link; `fromColumn` / `toColumn` are always the first pair.
|
|
129
|
+
*/
|
|
130
|
+
pairs?: ColumnPair[];
|
|
131
|
+
/** The composite's `compositeKey` name, alongside `pairs`. */
|
|
132
|
+
compositeKey?: string;
|
|
122
133
|
/** Stage of the owning domain canvas — drives CSS colour class. */
|
|
123
134
|
stage?: Stage;
|
|
124
135
|
/** Discrepancy status for ghost/extra/mismatch edges. */
|
|
@@ -129,6 +140,11 @@ export type FkEdgeData = {
|
|
|
129
140
|
readOnly?: boolean;
|
|
130
141
|
/** True when source === target — renders a loop arc over the top-right corner. */
|
|
131
142
|
isSelfLoop?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* For a self-loop: its place among the node's self-loops, by edge id (#133
|
|
145
|
+
* L3). Each one is drawn wider than the last, so loops nest, never cross.
|
|
146
|
+
*/
|
|
147
|
+
loopIndex?: number;
|
|
132
148
|
/** Index signature required by React Flow's Edge generic. */
|
|
133
149
|
[key: string]: unknown;
|
|
134
150
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erd-studio/renderer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "ERD Studio's React Flow canvas: the entity-relationship diagram renderer shared by the VS Code extension, with a read-only viewer mode.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"prepack": "npm run build"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@erd-studio/core": "0.1.
|
|
49
|
+
"@erd-studio/core": "0.1.3",
|
|
50
50
|
"zustand": "^5.0.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|