dbdocs 1.6.2__tar.gz → 1.7.0__tar.gz
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.
- {dbdocs-1.6.2 → dbdocs-1.7.0}/PKG-INFO +1 -1
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/core/artifacts.py +2 -2
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/erd.py +25 -23
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/builder.py +6 -1
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/css/style.css +10 -4
- dbdocs-1.7.0/dbdocs/site/bundle/assets/graph/index.css +1 -0
- dbdocs-1.7.0/dbdocs/site/bundle/assets/graph/index.js +62 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/js/data/data.js +1 -1
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/js/service/service.js +1 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/js/ui/overlays.js +4 -1
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/js/ui/ui.js +17 -3
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/deploy.py +1 -1
- dbdocs-1.6.2/dbdocs/site/bundle/assets/graph/index.css +0 -1
- dbdocs-1.6.2/dbdocs/site/bundle/assets/graph/index.js +0 -62
- {dbdocs-1.6.2 → dbdocs-1.7.0}/.gitignore +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/LICENSE +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/README.md +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/__main__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/cli/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/cli/main.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/core/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/core/config.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/core/exceptions.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/core/log.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/_sqlglot_lineage.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/column_lineage.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/graph.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/dimensions.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/extractor.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/base.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/dimensions/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/dimensions/documentation.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/dimensions/governance.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/dimensions/modeling.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/dimensions/performance.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/dimensions/structure.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/dimensions/testing.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/health/rules/registry.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/nodes.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/extract/tests_index.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/main.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/__init__.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/api_schema.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/css/icons.css +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/favicon.svg +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/js/app.js +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/js/ui/dom.js +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/vendor/marked.min.js +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/assets/vendor/minisearch.min.js +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/bundle/index.html +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/dbdocs/site/inject.py +0 -0
- {dbdocs-1.6.2 → dbdocs-1.7.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbdocs
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Alternative dbt docs site: Catalog + ERD + column-level lineage
|
|
5
5
|
Project-URL: Homepage, https://github.com/datnguye/dbt-docs
|
|
6
6
|
Project-URL: Repository, https://github.com/datnguye/dbt-docs
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Loading dbt artifacts (manifest/catalog) via the dbterd parser.
|
|
2
2
|
|
|
3
|
-
dbterd parses ``manifest.json`` / ``catalog.json`` into ``
|
|
3
|
+
dbterd parses ``manifest.json`` / ``catalog.json`` into ``artifact_parser``
|
|
4
4
|
Pydantic models. Two cross-cutting gotchas live here so the rest of dbdocs never
|
|
5
5
|
has to think about them:
|
|
6
6
|
|
|
7
|
-
* **Schema field aliasing.** ``
|
|
7
|
+
* **Schema field aliasing.** ``artifact_parser`` aliases the ``schema``
|
|
8
8
|
field to ``schema_`` to avoid clobbering Pydantic's ``BaseModel.schema()`` —
|
|
9
9
|
so ``node.schema`` is a *bound method*, not the value. Always read
|
|
10
10
|
``node.schema_``; :func:`db_schema` centralizes that.
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"""Structured ERD data via dbterd's official ``json`` target.
|
|
2
2
|
|
|
3
|
-
dbterd
|
|
3
|
+
dbterd ships a built-in, schema-validated ``json`` target that emits
|
|
4
4
|
``{nodes, edges, metadata}``. Each node carries ``id`` (the dbt unique_id),
|
|
5
5
|
``name``, ``schema_name``, ``database``, ``resource_type``, and ``columns``
|
|
6
6
|
(with ``data_type``, ``is_primary_key``, ``is_foreign_key``). Each edge carries
|
|
7
7
|
``id``, ``from_id`` (FK/child side), ``to_id`` (referenced/parent side),
|
|
8
|
-
``from_columns``, ``to_columns``, ``label``, and
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
``from_columns``, ``to_columns``, ``label``, ``relationship_type``, and
|
|
9
|
+
``cardinality``.
|
|
10
|
+
|
|
11
|
+
``build_erd_data`` maps that shape into the SPA's ``{nodes, edges}`` using
|
|
12
|
+
dbterd's own field names throughout — no renaming. The React Flow bundle reads
|
|
13
|
+
``nodes`` (entities + column flags) and ``edges`` (FK relationships, keyed by
|
|
14
|
+
dbt unique_id in ``from_id``/``to_id``) and maps ``from_id``/``to_id`` to React
|
|
15
|
+
Flow's ``source``/``target`` on its own side, with the parent/referenced node
|
|
16
|
+
as ``to_id``.
|
|
13
17
|
"""
|
|
14
18
|
|
|
15
19
|
import json
|
|
@@ -54,17 +58,14 @@ def build_erd_data(erd: DbtErd) -> dict:
|
|
|
54
58
|
When ``entity_name_format`` is configured, dbterd emits edge ``from_id`` /
|
|
55
59
|
``to_id`` as the formatted entity name (e.g. ``orders``) rather than the
|
|
56
60
|
full unique_id (e.g. ``model.jaffle_shop.orders``). A ``name_to_id`` map
|
|
57
|
-
resolves those short names back to the node id before building edges, so
|
|
58
|
-
|
|
61
|
+
resolves those short names back to the node id before building edges, so
|
|
62
|
+
the emitted ``from_id``/``to_id`` always reference a valid node ``id``.
|
|
59
63
|
|
|
60
64
|
Some dbterd algos (e.g. ``model_contract``) do not set ``is_foreign_key``
|
|
61
65
|
on node columns even when those columns appear in FK edges. After building
|
|
62
66
|
edges, any column named in an edge's ``from_columns`` (the FK/child side)
|
|
63
|
-
has its ``is_foreign_key`` flag back-filled to ``True`` on the
|
|
64
|
-
|
|
65
|
-
SPA edge direction: ``source`` is the referenced (parent) side, ``target``
|
|
66
|
-
is the FK (child) side — matching dbterd's ``to_id`` and ``from_id``
|
|
67
|
-
respectively.
|
|
67
|
+
has its ``is_foreign_key`` flag back-filled to ``True`` on the ``from_id``
|
|
68
|
+
node.
|
|
68
69
|
"""
|
|
69
70
|
payload = json.loads(erd.get_erd())
|
|
70
71
|
raw_nodes = payload.get("nodes", [])
|
|
@@ -95,13 +96,13 @@ def _backfill_fk_flags(nodes: "list[dict]", edges: "list[dict]") -> None:
|
|
|
95
96
|
"""
|
|
96
97
|
nodes_by_id = {n["id"]: n for n in nodes}
|
|
97
98
|
for edge in edges:
|
|
98
|
-
|
|
99
|
-
if
|
|
99
|
+
from_node = nodes_by_id.get(edge["from_id"])
|
|
100
|
+
if from_node is None:
|
|
100
101
|
continue
|
|
101
102
|
fk_cols = {c.lower() for c in edge.get("from_columns", [])}
|
|
102
103
|
if not fk_cols:
|
|
103
104
|
continue
|
|
104
|
-
for col in
|
|
105
|
+
for col in from_node["columns"]:
|
|
105
106
|
if col["name"].lower() in fk_cols:
|
|
106
107
|
col["is_foreign_key"] = True
|
|
107
108
|
|
|
@@ -111,12 +112,12 @@ def _build_node(node: dict) -> dict:
|
|
|
111
112
|
"id": node["id"],
|
|
112
113
|
"label": node.get("name") or "",
|
|
113
114
|
"database": node.get("database") or "",
|
|
114
|
-
"
|
|
115
|
+
"schema_name": node.get("schema_name") or "",
|
|
115
116
|
"resource_type": node.get("resource_type") or "model",
|
|
116
117
|
"columns": [
|
|
117
118
|
{
|
|
118
119
|
"name": c["name"],
|
|
119
|
-
"
|
|
120
|
+
"data_type": c.get("data_type") or "",
|
|
120
121
|
"description": c.get("description") or "",
|
|
121
122
|
"is_primary_key": bool(c.get("is_primary_key")),
|
|
122
123
|
"is_foreign_key": bool(c.get("is_foreign_key")),
|
|
@@ -135,16 +136,17 @@ def _resolve_edge_id(raw: str, node_ids: "set[str]", name_to_id: "dict[str, str]
|
|
|
135
136
|
|
|
136
137
|
|
|
137
138
|
def _build_edge(edge: dict, index: int, node_ids: "set[str]", name_to_id: "dict[str, str]") -> dict:
|
|
138
|
-
# from_id is the FK/child side; to_id is the referenced/parent side
|
|
139
|
-
#
|
|
139
|
+
# from_id is the FK/child side; to_id is the referenced/parent side —
|
|
140
|
+
# dbterd's own convention, carried through unchanged.
|
|
140
141
|
raw_from = edge.get("from_id") or ""
|
|
141
142
|
raw_to = edge.get("to_id") or ""
|
|
142
143
|
return {
|
|
143
144
|
"id": edge.get("id") or f"e{index}",
|
|
144
|
-
"
|
|
145
|
-
"
|
|
145
|
+
"from_id": _resolve_edge_id(raw_from, node_ids, name_to_id),
|
|
146
|
+
"to_id": _resolve_edge_id(raw_to, node_ids, name_to_id),
|
|
146
147
|
"from_columns": edge.get("from_columns") or [],
|
|
147
148
|
"to_columns": edge.get("to_columns") or [],
|
|
148
149
|
"label": edge.get("label") or "",
|
|
149
|
-
"
|
|
150
|
+
"relationship_type": edge.get("relationship_type") or "",
|
|
151
|
+
"cardinality": edge.get("cardinality") or "",
|
|
150
152
|
}
|
|
@@ -211,7 +211,7 @@ class ReportBuilder:
|
|
|
211
211
|
counts[label] = counts.get(label, 0) + len(collection)
|
|
212
212
|
return counts
|
|
213
213
|
|
|
214
|
-
def generate(self, output_dir: "str | None" = None) -> str:
|
|
214
|
+
def generate(self, output_dir: "str | None" = None, versioned: bool = False) -> str:
|
|
215
215
|
"""Render the site into ``output_dir`` (or config's). Returns its path.
|
|
216
216
|
|
|
217
217
|
The data dict is written as an external ``dbdocs-data.json.gz`` that the
|
|
@@ -225,6 +225,10 @@ class ReportBuilder:
|
|
|
225
225
|
see ``_write_api`` for the layout. The api/ directory is created after the
|
|
226
226
|
bundle copytree (so the rmtree + copytree cycle doesn't touch it) and is
|
|
227
227
|
not shipped in the wheel.
|
|
228
|
+
|
|
229
|
+
``versioned=True`` is set by ``deploy()`` to signal that a
|
|
230
|
+
``versions.json`` sibling exists; the SPA gates its version-switcher fetch
|
|
231
|
+
on this flag so a plain ``generate`` build never makes a 404 request.
|
|
228
232
|
"""
|
|
229
233
|
out = Path(output_dir) if output_dir else Path(self.config.output_path)
|
|
230
234
|
if out.exists():
|
|
@@ -233,6 +237,7 @@ class ReportBuilder:
|
|
|
233
237
|
copytree(src=BUNDLE_DIR, dst=out, dirs_exist_ok=True)
|
|
234
238
|
|
|
235
239
|
data = self.build_data()
|
|
240
|
+
data["metadata"]["versioned"] = versioned
|
|
236
241
|
data["metadata"].update(self._stage_branding(out))
|
|
237
242
|
index = out / "index.html"
|
|
238
243
|
index.write_text(strip_marker(index.read_text(encoding="utf-8")), encoding="utf-8")
|
|
@@ -190,7 +190,11 @@ body.nav-collapsed .nav-collapse { display: none; }
|
|
|
190
190
|
}
|
|
191
191
|
.nav-reopen:hover { color: var(--accent); border-color: var(--accent); }
|
|
192
192
|
body.nav-collapsed .nav-reopen { display: inline-flex; }
|
|
193
|
-
.sidebar {
|
|
193
|
+
.sidebar {
|
|
194
|
+
flex: 1; min-height: 0; overflow: hidden; padding: 12px;
|
|
195
|
+
display: flex; flex-direction: column;
|
|
196
|
+
}
|
|
197
|
+
.sidebar > .nav-cta, .sidebar > .nav-tabs, .sidebar > .nav-filter { flex-shrink: 0; }
|
|
194
198
|
.nav-filter {
|
|
195
199
|
width: 100%; box-sizing: border-box; margin: 6px 0 10px;
|
|
196
200
|
padding: 7px 10px; font-size: 13px; border-radius: var(--radius);
|
|
@@ -289,7 +293,7 @@ body.nav-collapsed .nav-reopen { display: inline-flex; }
|
|
|
289
293
|
.nav-tab:hover { color: var(--text); background: var(--bg-elev); }
|
|
290
294
|
.nav-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
291
295
|
.nav-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
292
|
-
.nav-tab-panel { min-height: 0; }
|
|
296
|
+
.nav-tab-panel { flex: 1; min-height: 0; overflow-y: auto; }
|
|
293
297
|
|
|
294
298
|
/* Resource-type dot */
|
|
295
299
|
.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; display: inline-block; }
|
|
@@ -460,8 +464,10 @@ details.node-section:last-of-type { border-bottom: none; }
|
|
|
460
464
|
/* Graph host (React Flow bundle mounts here) */
|
|
461
465
|
.graph-host { width: 100%; }
|
|
462
466
|
/* Fullscreen: the host fills the screen; its inner .dbd-graph grows to match. */
|
|
463
|
-
.graph-host:fullscreen
|
|
464
|
-
.
|
|
467
|
+
.graph-host:fullscreen,
|
|
468
|
+
.erd-section-host:fullscreen { background: var(--bg); padding: 0; }
|
|
469
|
+
.graph-host:fullscreen .dbd-graph,
|
|
470
|
+
.erd-section-host:fullscreen .dbd-graph { height: 100vh; border: none; border-radius: 0; }
|
|
465
471
|
|
|
466
472
|
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
467
473
|
.page-head h2 { border-bottom: none; padding-bottom: 0; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.erd-table{background:var(--erd-node-bg, #1e1e1e);color:var(--erd-node-fg, #d4d4d4);border:1px solid var(--erd-border, #007acc);border-radius:6px;font-family:var(--erd-font-mono, monospace);font-size:12px;min-width:220px;box-shadow:0 1px 3px var(--erd-shadow, rgba(0, 0, 0, .25))}.erd-table[data-resource=source]{border-style:dashed}.erd-table[data-filter=match]{border-color:var(--erd-accent, #e5c07b);box-shadow:0 0 0 2px var(--erd-accent, #e5c07b),0 1px 4px var(--erd-shadow, rgba(0, 0, 0, .35))}.erd-table[data-filter=dim]{opacity:.32;filter:saturate(.6);transition:opacity .12s ease,filter .12s ease}.erd-table[data-filter=dim]:hover{opacity:.85;filter:none}.erd-table[data-active=true]{border-color:var(--erd-border, #007acc);box-shadow:0 0 0 2px var(--erd-border, #007acc),0 2px 8px var(--erd-shadow, rgba(0, 0, 0, .4))}.erd-table[data-active=true][data-filter=match]{box-shadow:0 0 0 2px var(--erd-border, #007acc),inset 0 0 0 1px var(--erd-accent, #e5c07b)}.erd-table-header{display:flex;align-items:center;gap:6px;box-sizing:border-box;height:32px;padding:6px 10px;background:var(--erd-header-bg, #252526);border-bottom:1px solid var(--erd-border, #007acc);cursor:pointer;-webkit-user-select:none;user-select:none}.erd-table-header:hover{background:var(--erd-hover-bg, #2a2d2e)}.erd-table-header:focus-visible{outline:2px solid var(--erd-border, #007acc);outline-offset:-2px}.erd-table-name{font-weight:600}.erd-table-schema{margin-left:auto;color:var(--erd-muted-fg, #808080);font-size:11px}.erd-table-columns{list-style:none;margin:0;padding:4px 0}.erd-column{display:grid;grid-template-columns:28px 1fr auto;align-items:center;gap:6px;box-sizing:border-box;height:22px;padding:0 10px;position:relative}.erd-column[data-highlighted=true]{background:color-mix(in srgb,var(--erd-accent, #e5c07b) 16%,transparent);box-shadow:inset 2px 0 0 var(--erd-accent, #e5c07b)}.erd-column[data-highlighted=true] .erd-column-name{color:var(--erd-accent, #e5c07b);font-weight:600}.erd-column-badge{font-size:10px;font-weight:700;text-align:center}.badge-pk{color:var(--erd-accent, #e5c07b)}.badge-fk{color:var(--erd-fk, #61afef)}.badge-none{color:transparent}.erd-column-type{color:var(--erd-muted-fg, #808080)}.erd-table-icon{display:inline-flex;align-items:center;color:var(--erd-icon, #FF694A)}.erd-table-more{box-sizing:border-box;height:22px;padding:0 10px;border-top:1px solid var(--erd-divider, #3c3c3c);display:flex;align-items:center;color:var(--erd-muted-fg, #808080);font-size:11px;font-style:italic}.erd-table[data-dimmed=true]{opacity:.25;transition:opacity .12s ease}.erd-table-expand{display:block;width:100%;box-sizing:border-box;height:24px;padding:0 10px;background:transparent;color:var(--erd-link-fg, #3794ff);border:none;border-top:1px dashed var(--erd-divider, #3c3c3c);text-align:center;cursor:pointer;font-size:11px;font-family:inherit}.erd-table-expand:hover{background:var(--erd-hover-bg, #2a2d2e)}.erd-table .react-flow__handle.erd-hidden-handle{width:1px;height:1px;min-width:0;min-height:0;border:none;background:transparent;opacity:0;pointer-events:none}.erd-flow{width:100%;height:100%;min-height:0;position:relative}.erd-flow .react-flow{width:100%;height:100%}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}.dbd-graph{display:flex;flex-direction:column;height:72vh;border:1px solid var(--border, #e3e6ea);border-radius:8px;overflow:hidden;background:var(--bg-soft, #f5f6f8)}.dbd-canvas{flex:1;min-height:0}.dbd-graph-empty{display:flex;align-items:center;justify-content:center;height:100%;padding:24px}.dbd-graph-empty p{max-width:32rem;text-align:center;color:var(--text-soft, #5a6473)}.dbd-erd-bar{display:flex;justify-content:flex-end;padding:6px 8px;border-bottom:1px solid var(--border, #e3e6ea);background:var(--bg-elev, #fff)}.dbd-lock-btn{padding:4px 12px;border:1px solid var(--border, #e3e6ea);border-radius:6px;background:var(--bg-soft, #f5f6f8);color:var(--text-soft, #5b636c);font-size:12px;cursor:pointer}.dbd-lock-btn:hover{border-color:var(--accent, #2f6feb)}.dbd-lock-btn.on{color:var(--accent, #2f6feb);border-color:var(--accent, #2f6feb)}.react-flow__edge{cursor:pointer}.react-flow__edge:hover .react-flow__edge-path{stroke-width:2.5}.react-flow__attribution{display:none}.react-flow__controls{box-shadow:0 1px 4px #00000040;border-radius:6px;overflow:hidden}.react-flow__controls-button{background:var(--bg-elev, #fff);border-bottom:1px solid var(--border, #e3e6ea);color:var(--text, #1c2024);fill:var(--text, #1c2024);width:26px;height:26px}.react-flow__controls-button:hover{background:var(--bg-soft, #f5f6f8)}.react-flow__controls-button svg{fill:currentColor;max-width:14px;max-height:14px}.react-flow__minimap{background:var(--bg-elev, #fff);border:1px solid var(--border, #e3e6ea);border-radius:6px}.react-flow__minimap-mask{fill:#7f7f7f2e}.react-flow__background{color:var(--border, #e3e6ea)}.dbd-toolbar{display:flex;flex-wrap:wrap;gap:10px;padding:8px 10px;border-bottom:1px solid var(--border, #e3e6ea);background:var(--bg-elev, #fff)}.dbd-toolbar input,.dbd-toolbar select{padding:5px 9px;border:1px solid var(--border, #e3e6ea);border-radius:6px;background:var(--bg-soft, #f5f6f8);color:var(--text, #1c2024);font-size:13px}.dbd-layer-seg{display:flex;border:1px solid var(--border, #e3e6ea);border-radius:6px;overflow:hidden;background:var(--bg-soft, #f5f6f8)}.dbd-layer-seg button{padding:5px 10px;border:none;border-right:1px solid var(--border, #e3e6ea);background:transparent;color:var(--text-soft, #5b636c);font-size:13px;cursor:pointer;white-space:nowrap}.dbd-layer-seg button:last-child{border-right:none}.dbd-layer-seg button:hover{background:var(--bg-soft, #f0f1f3);color:var(--text, #1c2024)}.dbd-layer-seg button.active{background:var(--accent, #2f6feb);color:#fff}.dbd-multi{position:relative}.dbd-multi-trigger{display:flex;align-items:center;gap:4px;padding:5px 9px;border:1px solid var(--border, #e3e6ea);border-radius:6px;background:var(--bg-soft, #f5f6f8);color:var(--text, #1c2024);font-size:13px;cursor:pointer;white-space:nowrap}.dbd-multi-trigger:hover,.dbd-multi-trigger[aria-expanded=true]{border-color:var(--accent, #2f6feb)}.dbd-multi-arrow{font-size:10px;color:var(--text-soft, #5b636c)}.dbd-multi-panel{position:absolute;top:calc(100% + 4px);left:0;z-index:100;min-width:180px;padding:4px 0;border:1px solid var(--border, #e3e6ea);border-radius:6px;background:var(--bg-elev, #fff);box-shadow:0 4px 12px #0000001f}.dbd-multi-option{display:flex;align-items:center;gap:7px;padding:5px 12px;cursor:pointer;font-size:13px;color:var(--text, #1c2024);-webkit-user-select:none;user-select:none}.dbd-multi-option:hover{background:var(--bg-soft, #f5f6f8)}.dbd-multi-all{border-bottom:1px solid var(--border, #e3e6ea);margin-bottom:2px;font-weight:600}.dbd-multi-label{flex:1}.dbd-multi-count{color:var(--text-soft, #5b636c);font-size:11px}.dbd-multi-group{padding:4px 0 2px}.dbd-multi-group:not(:first-child){border-top:1px solid var(--border, #e3e6ea)}.dbd-multi-group-legend{padding:4px 12px 2px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-soft, #5b636c)}.dbd-node{display:flex;align-items:center;gap:7px;min-width:150px;padding:7px 12px;border:1px solid var(--border, #e3e6ea);border-radius:6px;background:var(--bg-elev, #fff);color:var(--text, #1c2024);font-size:12px}.dbd-node-bar{width:4px;height:18px;border-radius:2px}.dbd-node-label{font-weight:600;flex:1}.dbd-node-badge{padding:1px 5px;border-radius:3px;font-size:9px;font-weight:700;color:#fff;text-transform:uppercase;letter-spacing:.04em;flex-shrink:0}.dbd-dim{opacity:.25}.dbd-graph .erd-table{min-width:210px;border-radius:8px;overflow:hidden;font-family:inherit;box-shadow:none}.dbd-graph .erd-table[data-resource=source]{border-style:solid}.dbd-graph .erd-table[data-focus=true]{box-shadow:0 0 0 2px var(--accent, #2f6feb)}.dbd-graph .erd-table-header{gap:8px;border-bottom:none;border-top:3px solid var(--border, #888)}.dbd-graph .erd-table[data-resource=model] .erd-table-header{border-top-color:var(--model, #2f6feb)}.dbd-graph .erd-table[data-resource=source] .erd-table-header{border-top-color:var(--source, #16a34a)}.dbd-graph .erd-table[data-resource=seed] .erd-table-header{border-top-color:var(--seed, #b45309)}.dbd-graph .erd-table[data-resource=snapshot] .erd-table-header{border-top-color:var(--snapshot, #7c3aed)}.dbd-graph .erd-table-icon{display:none}.dbd-graph .erd-table-name{font-weight:700}.dbd-graph .erd-column{grid-template-columns:auto 1fr auto;gap:8px;border-top:1px solid var(--border, #e3e6ea)}.dbd-graph .erd-column-name{font-family:monospace}.dbd-graph .erd-column-type{font-size:11px}.dbd-graph .erd-column-badge{display:inline-flex;justify-content:center;min-width:22px;padding:0 5px;border-radius:4px;color:#fff}.dbd-graph .erd-column-badge.badge-pk{background:var(--seed, #b45309);color:#fff}.dbd-graph .erd-column-badge.badge-fk{background:var(--accent, #2f6feb);color:#fff}.dbd-graph .erd-column-badge.badge-none{background:transparent}.dbd-graph .react-flow__edge:hover{--erd-edge-width: 2.5}
|