drizzle-cube
Version:
Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.
552 lines (551 loc) • 18.9 kB
JavaScript
import { jsxs as o, jsx as t, Fragment as J } from "react/jsx-runtime";
import { useMemo as j, useState as H, useEffect as T, useCallback as S } from "react";
import Z, { Handle as P, Position as g, getBezierPath as K, BaseEdge as Q, EdgeLabelRenderer as U, useNodesState as _, useEdgesState as ee, addEdge as te, ConnectionMode as se, Controls as ne, MiniMap as re, Background as oe, BackgroundVariant as ae } from "reactflow";
import { u as ie } from "./charts-B8YMw1mi.js";
import O from "dagre";
function ce({ data: i }) {
const { cube: s, onFieldClick: f, isHighlighted: n, highlightedFields: w, searchTerm: m } = i, d = () => {
}, y = (e, r) => {
f && f(s.name, e, r);
}, l = (e) => w.includes(e), b = (e) => {
if (!m?.trim()) return !0;
const r = m.toLowerCase();
return e.name.toLowerCase().includes(r) || e.title && e.title.toLowerCase().includes(r);
}, k = (() => {
if (!m?.trim()) return !0;
const e = s.measures.some((c) => b(c)), r = s.dimensions.some((c) => b(c));
return e || r;
})(), N = (e, r, c) => {
const v = b(e), u = "px-4 py-2 text-xs cursor-pointer transition-all border-b border-dc-border last:border-b-0";
if (!k && m?.trim()) {
if (r) {
if (c === "measure")
return `${u} bg-dc-warning-bg text-dc-warning`;
if (c === "dimension")
return e.type === "time" ? `${u} bg-dc-accent-bg text-dc-accent` : `${u} bg-dc-success-bg text-dc-success`;
}
return `${u} hover:bg-dc-surface-hover text-dc-text-secondary`;
}
if (m?.trim() && !v)
return `${u} opacity-40 hover:opacity-60 text-dc-text-muted`;
if (m?.trim() && v && !r)
return `${u} font-bold hover:bg-dc-accent-bg`;
if (r) {
if (c === "measure")
return `${u} bg-dc-warning-bg text-dc-warning font-semibold`;
if (c === "dimension")
return e.type === "time" ? `${u} bg-dc-accent-bg text-dc-accent font-semibold` : `${u} bg-dc-success-bg text-dc-success font-semibold`;
}
return `${u} hover:bg-dc-surface-hover text-dc-text-secondary`;
};
return /* @__PURE__ */ o(
"div",
{
className: `
border-2 rounded-lg shadow-lg min-w-[280px] overflow-hidden transition-all
${!k && m?.trim() ? "opacity-30 grayscale" : ""}
${n ? "border-dc-accent ring-2 ring-dc-accent" : "border-dc-border"}
`,
style: {
backgroundColor: "var(--dc-surface)"
},
children: [
/* @__PURE__ */ t(
"div",
{
className: `
px-4 py-3 cursor-pointer transition-colors
${n ? "bg-dc-accent-bg hover:bg-dc-accent-bg" : "bg-dc-surface-secondary hover:bg-dc-surface-hover"}
`,
onClick: d,
children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ o("div", { children: [
/* @__PURE__ */ t("h3", { className: "font-semibold text-dc-text text-sm", children: s.title || s.name }),
s.description && /* @__PURE__ */ t("p", { className: "text-xs text-dc-text-muted mt-1 line-clamp-2", children: s.description })
] }),
/* @__PURE__ */ o("div", { className: "text-xs text-dc-text-muted ml-2", children: [
/* @__PURE__ */ o("div", { children: [
s.measures.length,
"M"
] }),
/* @__PURE__ */ o("div", { children: [
s.dimensions.length,
"D"
] })
] })
] })
}
),
s.measures.length > 0 && /* @__PURE__ */ o("div", { className: "border-t border-dc-border", children: [
/* @__PURE__ */ t("div", { className: "px-4 py-2 bg-dc-warning-bg border-b border-dc-border", children: /* @__PURE__ */ o("h4", { className: "text-xs font-medium text-dc-warning flex items-center", children: [
/* @__PURE__ */ t("span", { className: "w-2 h-2 bg-dc-warning-bg0 rounded-full mr-2" }),
"Measures (",
s.measures.length,
")"
] }) }),
/* @__PURE__ */ t("div", { className: "max-h-32 overflow-y-auto", children: s.measures.map((e) => {
const r = e.name.split(".")[1] || e.name, c = l(e.name);
return /* @__PURE__ */ t(
"div",
{
className: N(e, c, "measure"),
onClick: () => y(r, "measure"),
title: e.title,
children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ t("span", { className: "font-mono truncate", children: e.shortTitle || e.title || r }),
/* @__PURE__ */ t("span", { className: "text-dc-text-muted ml-2 text-[10px] uppercase", children: e.type })
] })
},
e.name
);
}) })
] }),
s.dimensions.filter((e) => e.type === "time").length > 0 && /* @__PURE__ */ o("div", { className: "border-t border-dc-border", children: [
/* @__PURE__ */ t("div", { className: "px-4 py-2 bg-dc-accent-bg border-b border-dc-border", children: /* @__PURE__ */ o("h4", { className: "text-xs font-medium text-dc-accent flex items-center", children: [
/* @__PURE__ */ t("span", { className: "w-2 h-2 bg-dc-accent-bg0 rounded-full mr-2" }),
"Time Dimensions (",
s.dimensions.filter((e) => e.type === "time").length,
")"
] }) }),
/* @__PURE__ */ t("div", { className: "max-h-32 overflow-y-auto", children: s.dimensions.filter((e) => e.type === "time").map((e) => {
const r = e.name.split(".")[1] || e.name, c = l(e.name);
return /* @__PURE__ */ t(
"div",
{
className: N(e, c, "dimension"),
onClick: () => y(r, "dimension"),
title: e.title,
children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ t("span", { className: "font-mono truncate", children: e.shortTitle || e.title || r }),
/* @__PURE__ */ t("span", { className: "text-dc-text-muted ml-2 text-[10px] uppercase", children: e.type })
] })
},
e.name
);
}) })
] }),
s.dimensions.filter((e) => e.type !== "time").length > 0 && /* @__PURE__ */ o("div", { className: "border-t border-dc-border", children: [
/* @__PURE__ */ t("div", { className: "px-4 py-2 bg-dc-success-bg border-b border-dc-border", children: /* @__PURE__ */ o("h4", { className: "text-xs font-medium text-dc-success flex items-center", children: [
/* @__PURE__ */ t("span", { className: "w-2 h-2 bg-dc-success-bg0 rounded-full mr-2" }),
"Dimensions (",
s.dimensions.filter((e) => e.type !== "time").length,
")"
] }) }),
/* @__PURE__ */ t("div", { className: "max-h-32 overflow-y-auto", children: s.dimensions.filter((e) => e.type !== "time").map((e) => {
const r = e.name.split(".")[1] || e.name, c = l(e.name);
return /* @__PURE__ */ t(
"div",
{
className: N(e, c, "dimension"),
onClick: () => y(r, "dimension"),
title: e.title,
children: /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ t("span", { className: "font-mono truncate", children: e.shortTitle || e.title || r }),
/* @__PURE__ */ t("span", { className: "text-dc-text-muted ml-2 text-[10px] uppercase", children: e.type })
] })
},
e.name
);
}) })
] }),
/* @__PURE__ */ t(
P,
{
type: "source",
position: g.Right,
id: "right",
className: "opacity-0",
isConnectable: !1
}
),
/* @__PURE__ */ t(
P,
{
type: "target",
position: g.Left,
id: "left",
className: "opacity-0",
isConnectable: !1
}
),
/* @__PURE__ */ t(
P,
{
type: "source",
position: g.Bottom,
id: "bottom",
className: "opacity-0",
isConnectable: !1
}
),
/* @__PURE__ */ t(
P,
{
type: "target",
position: g.Top,
id: "top",
className: "opacity-0",
isConnectable: !1
}
)
]
}
);
}
function de({
sourceX: i,
sourceY: s,
targetX: f,
targetY: n,
sourcePosition: w,
targetPosition: m,
style: d = {},
data: y,
markerEnd: l
}) {
const [b, E, k] = K({
sourceX: i,
sourceY: s,
sourcePosition: w,
targetX: f,
targetY: n,
targetPosition: m
});
if (!y) return null;
const { relationship: N, joinFields: e } = y, r = (C) => {
switch (C) {
case "belongsTo":
return "∈";
// belongs to symbol
case "hasOne":
return "1:1";
case "hasMany":
return "1:M";
default:
return "?";
}
}, v = ((C) => {
switch (C) {
case "belongsTo":
return "#10b981";
// green
case "hasOne":
return "#3b82f6";
// blue
case "hasMany":
return "#f59e0b";
// amber
default:
return "#6b7280";
}
})(N.relationship), u = r(N.relationship);
return /* @__PURE__ */ o(J, { children: [
/* @__PURE__ */ t(Q, { path: b, markerEnd: l, style: { ...d, stroke: v } }),
/* @__PURE__ */ t(U, { children: /* @__PURE__ */ t(
"div",
{
style: {
position: "absolute",
transform: `translate(-50%, -50%) translate(${E}px,${k}px)`,
fontSize: 10,
pointerEvents: "all"
},
className: "nodrag nopan",
children: /* @__PURE__ */ t(
"div",
{
className: "border-2 rounded-md px-2 py-1 shadow-xs",
style: {
backgroundColor: "var(--dc-surface)",
borderColor: v
},
children: /* @__PURE__ */ o("div", { className: "text-center", children: [
/* @__PURE__ */ t(
"div",
{
className: "font-bold text-xs mb-1",
style: { color: v },
children: u
}
),
/* @__PURE__ */ t("div", { className: "text-[9px] text-dc-text-muted leading-tight", children: e.map((C, B) => /* @__PURE__ */ o("div", { className: "font-mono", children: [
C.sourceField,
" → ",
C.targetField
] }, B)) })
] })
}
)
}
) })
] });
}
const le = {
direction: "TB",
nodeWidth: 320,
nodeHeight: 220,
nodeSep: 80,
rankSep: 150,
ranker: "network-simplex"
};
function ue(i, s, f = {}) {
const n = j(() => ({ ...le, ...f }), [f]), { layoutedNodes: w, layoutedEdges: m } = j(() => {
if (i.length === 0)
return { layoutedNodes: [], layoutedEdges: [] };
const d = new O.graphlib.Graph();
return d.setDefaultEdgeLabel(() => ({})), d.setGraph({
rankdir: n.direction,
nodesep: n.nodeSep,
ranksep: n.rankSep,
ranker: n.ranker
}), i.forEach((l) => {
d.setNode(l.id, {
width: n.nodeWidth,
height: n.nodeHeight
});
}), s.forEach((l) => {
d.setEdge(l.source, l.target);
}), O.layout(d), {
layoutedNodes: i.map((l) => {
const b = d.node(l.id);
return {
...l,
targetPosition: me(n.direction),
sourcePosition: he(n.direction),
position: {
x: b.x - n.nodeWidth / 2,
y: b.y - n.nodeHeight / 2
}
};
}),
layoutedEdges: s
};
}, [i, s, n.direction, n.nodeSep, n.rankSep, n.ranker, n.nodeWidth, n.nodeHeight]);
return { nodes: w, edges: m };
}
function me(i) {
switch (i) {
case "TB":
return g.Top;
case "BT":
return g.Bottom;
case "LR":
return g.Left;
case "RL":
return g.Right;
default:
return g.Top;
}
}
function he(i) {
switch (i) {
case "TB":
return g.Bottom;
case "BT":
return g.Top;
case "LR":
return g.Right;
case "RL":
return g.Left;
default:
return g.Bottom;
}
}
const ge = {
cubeNode: ce
}, pe = {
relationshipEdge: de
};
function Ce({
className: i = "",
onCubeClick: s,
onFieldClick: f,
highlightedCubes: n = [],
highlightedFields: w = [],
searchTerm: m
}) {
const { meta: d, metaLoading: y, metaError: l } = ie(), [b, E] = H({}), [k, N] = H(!1), [e, r] = H(null);
T(() => {
try {
const a = localStorage.getItem("drizzle-cube-erd-node-positions");
a && E(JSON.parse(a));
} catch {
}
}, []);
const { nodes: c, edges: v } = j(() => {
if (!d) return { nodes: [], edges: [] };
const a = [], x = [];
return d.cubes.forEach((h, p) => {
a.push({
id: h.name,
type: "cubeNode",
position: b[h.name] || { x: p % 3 * 400, y: Math.floor(p / 3) * 300 },
data: {
cube: h,
onCubeClick: s,
onFieldClick: f,
isHighlighted: !1,
// Will be updated separately
highlightedFields: []
// Will be updated separately
}
});
}), d.cubes.forEach((h) => {
h.relationships && h.relationships.forEach((p, M) => {
if (p.relationship === "belongsTo")
return;
const q = `${h.name}-${p.targetCube}-${M}`;
x.push({
id: q,
source: h.name,
target: p.targetCube,
type: "relationshipEdge",
data: {
relationship: p,
joinFields: p.joinFields
},
animated: !1,
style: {
stroke: be(p.relationship),
strokeWidth: 2
}
});
});
}), { nodes: a, edges: x };
}, [d, s, f, b]), u = k || Object.keys(b).length === 0, { nodes: C, edges: B } = ue(c, v, {
direction: "LR",
nodeWidth: 320,
nodeHeight: 220,
nodeSep: 100,
rankSep: 200
}), L = j(() => (u ? C : c).map((x) => ({
...x,
data: {
...x.data,
isHighlighted: n.includes(x.id),
highlightedFields: w,
searchTerm: m
}
})), [u, C, c, n, w, m]), F = u ? B : v;
T(() => {
if (k && L.length > 0) {
E({});
try {
localStorage.removeItem("drizzle-cube-erd-node-positions");
} catch {
}
N(!1);
}
}, [k, L]);
const [D, $, z] = _(L), [W, R, A] = ee(F);
T(() => {
$(L);
}, [L, $]), T(() => {
R(F);
}, [F, R]);
const I = S((a) => {
z(a), a.filter((h) => h.type === "position" && h.dragging === !1).length > 0 && $((h) => {
const p = {};
h.forEach((M) => {
M.position && (p[M.id] = M.position);
});
try {
localStorage.setItem("drizzle-cube-erd-node-positions", JSON.stringify(p));
} catch {
}
return E(p), h;
});
}, [z, $]), V = S(
(a) => R((x) => te(a, x)),
[R]
), G = S((a) => {
a.preventDefault(), a.stopPropagation(), console.log("Context menu triggered at:", a.clientX, a.clientY), r({
x: a.clientX,
y: a.clientY
});
}, []), X = S(() => {
e && r(null);
}, [e]), Y = S(() => {
N(!0), r(null);
}, []);
return y ? /* @__PURE__ */ t("div", { className: `flex items-center justify-center h-96 ${i}`, children: /* @__PURE__ */ o("div", { className: "text-center", children: [
/* @__PURE__ */ t("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-dc-accent mx-auto mb-2" }),
/* @__PURE__ */ t("p", { className: "text-dc-text-muted", children: "Loading cube schema..." })
] }) }) : l ? /* @__PURE__ */ t("div", { className: `flex items-center justify-center h-96 ${i}`, children: /* @__PURE__ */ o("div", { className: "text-center text-dc-error", children: [
/* @__PURE__ */ t("p", { className: "font-medium", children: "Failed to load cube schema" }),
/* @__PURE__ */ t("p", { className: "text-sm mt-1", children: l })
] }) }) : !d || d.cubes.length === 0 ? /* @__PURE__ */ t("div", { className: `flex items-center justify-center h-96 ${i}`, children: /* @__PURE__ */ o("div", { className: "text-center text-dc-text-muted", children: [
/* @__PURE__ */ t("p", { className: "font-medium", children: "No cubes found" }),
/* @__PURE__ */ t("p", { className: "text-sm mt-1", children: "Register some cubes to see the relationship diagram" })
] }) }) : /* @__PURE__ */ o("div", { className: `h-full ${i}`, style: { height: "600px" }, children: [
/* @__PURE__ */ t("div", { className: "h-full", children: /* @__PURE__ */ o(
Z,
{
nodes: D,
edges: W,
onNodesChange: I,
onEdgesChange: A,
onConnect: V,
nodeTypes: ge,
edgeTypes: pe,
connectionMode: se.Loose,
fitView: !0,
minZoom: 0.1,
maxZoom: 2,
defaultViewport: { x: 0, y: 0, zoom: 0.8 },
proOptions: { hideAttribution: !0 },
onPaneContextMenu: G,
onPaneClick: X,
children: [
/* @__PURE__ */ t(ne, {}),
/* @__PURE__ */ t(
re,
{
nodeColor: (a) => n.includes(a.id) ? "#8b5cf6" : "#e5e7eb",
maskColor: "rgb(240, 242, 246, 0.7)"
}
),
/* @__PURE__ */ t(oe, { variant: ae.Dots, gap: 12, size: 1 })
]
}
) }),
e && /* @__PURE__ */ t(
"div",
{
className: "fixed z-50 bg-dc-surface rounded-md shadow-lg border border-dc-border py-1 min-w-[120px]",
style: {
left: e.x,
top: e.y
},
children: /* @__PURE__ */ t(
"button",
{
onClick: Y,
className: "w-full px-3 py-2 text-sm text-dc-text-secondary hover:bg-dc-surface-hover text-left",
children: "Auto Layout"
}
)
}
)
] });
}
function be(i) {
switch (i) {
case "belongsTo":
return "#10b981";
// green
case "hasOne":
return "#3b82f6";
// blue
case "hasMany":
return "#f59e0b";
// amber
default:
return "#6b7280";
}
}
export {
Ce as CubeRelationshipDiagram,
Ce as default
};
//# sourceMappingURL=index-B7NSVb33.js.map