quickdbd-cli 0.1.2__tar.gz → 0.1.4__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.
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/PKG-INFO +103 -21
- quickdbd_cli-0.1.4/README.md +222 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/pyproject.toml +1 -1
- quickdbd_cli-0.1.4/src/quickdbd_cli/__init__.py +1 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/render.js +159 -2
- quickdbd_cli-0.1.4/src/quickdbd_cli/assets/style.css +167 -0
- quickdbd_cli-0.1.4/src/quickdbd_cli/assets/viewer.js +663 -0
- quickdbd_cli-0.1.4/src/quickdbd_cli/cli.py +326 -0
- quickdbd_cli-0.1.4/src/quickdbd_cli/dbt.py +412 -0
- quickdbd_cli-0.1.4/src/quickdbd_cli/diff.py +247 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/export.py +9 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/html.py +27 -4
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/measure.py +22 -2
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/project.py +42 -2
- quickdbd_cli-0.1.4/tests/fixtures/jaffle/LICENSE +201 -0
- quickdbd_cli-0.1.4/tests/fixtures/jaffle/README.md +2 -0
- quickdbd_cli-0.1.4/tests/fixtures/jaffle/catalog.json +227 -0
- quickdbd_cli-0.1.4/tests/fixtures/jaffle/manifest.json +880 -0
- quickdbd_cli-0.1.4/tests/test_dbt.py +231 -0
- quickdbd_cli-0.1.4/tests/test_diff.py +127 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/tests/test_pipeline.py +51 -0
- quickdbd_cli-0.1.4/tests/test_select.py +143 -0
- quickdbd_cli-0.1.2/README.md +0 -140
- quickdbd_cli-0.1.2/src/quickdbd_cli/__init__.py +0 -1
- quickdbd_cli-0.1.2/src/quickdbd_cli/assets/style.css +0 -92
- quickdbd_cli-0.1.2/src/quickdbd_cli/assets/viewer.js +0 -380
- quickdbd_cli-0.1.2/src/quickdbd_cli/cli.py +0 -169
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/.gitignore +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/LICENSE +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/NOTICE +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/examples/quickdbd-sample.txt +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/examples/synthetic/finance.txt +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/examples/synthetic/hr.txt +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/examples/synthetic/inventory.txt +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/examples/synthetic/marketing.txt +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/examples/synthetic/qdbd.layout.json +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/examples/synthetic/sales.txt +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/scripts/gen_font_metrics.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/scripts/gen_schema.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/scripts/release.sh +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/__main__.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/editor.js +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/assets/font-metrics.json +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/jsrt.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/layout.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/model.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/parser.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/src/quickdbd_cli/serve.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/tests/test_parser.py +0 -0
- {quickdbd_cli-0.1.2 → quickdbd_cli-0.1.4}/tests/test_serve.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quickdbd-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit.
|
|
5
5
|
Project-URL: Homepage, https://github.com/MarkPhamm/quickdbd-cli
|
|
6
6
|
Author: Minh (Mark) Pham
|
|
@@ -44,32 +44,96 @@ pipx install quickdbd-cli # or: uvx --from quickdbd-cli qdbd ...
|
|
|
44
44
|
qdbd build schema.txt -o schema.html
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
**Live demo (200 tables):** https://markphamm.github.io/quickdbd-cli/
|
|
48
|
+
|
|
47
49
|
Not affiliated with QuickDBD. It reads the same text syntax, so schemas move freely between the two.
|
|
48
50
|
|
|
49
51
|
## Features
|
|
50
52
|
|
|
51
53
|
- **Same syntax as QuickDBD:** tables, aliases, quoted names, all 9 relationship operators, `rel` lines, PK/FK/NULL/UNIQUE/INDEX/IDENTITY, `default=`, doc comments, `[settings]` and `[layout]` sections. Error messages are QuickDBD's own.
|
|
52
54
|
- **Interactive page:**
|
|
53
|
-
- search
|
|
55
|
+
- explorer sidebar: tables grouped by file or dbt folder, with search (`/`, Enter jumps to the best match)
|
|
56
|
+
- table detail panel (dbt-docs style, for relationships): description, columns with type/keys/docs, and clickable **References** / **Referenced by** lists
|
|
54
57
|
- minimap
|
|
55
58
|
- click a table to focus on it and its neighbours
|
|
56
59
|
- collapse tables: full / keys only / names only
|
|
57
60
|
- hover a column to highlight its relationship
|
|
58
61
|
- hover a dotted-underlined table or column to read its `#` doc comments
|
|
59
|
-
- PNG/SVG export buttons
|
|
62
|
+
- PNG/SVG export buttons, and **draw.io**: an editable diagrams.net file using draw.io's ER table shapes and crow's-foot arrows
|
|
63
|
+
- shareable links: the URL keeps the filter, selected table, collapse mode and zoom; **Link** copies it
|
|
60
64
|
- **Live editing:** `qdbd serve` reloads when you save, has a browser editor pane with autosave, and remembers where you drag tables.
|
|
61
65
|
- **Many files, one diagram:** point it at a folder and foreign keys can cross files.
|
|
62
66
|
- **Pip-only install:** no Graphviz, Node or browser needed. Layout uses [ELK](https://eclipse.dev/elk/), running in an embedded V8.
|
|
63
67
|
- **CI-friendly:** builds are deterministic, and errors are `file:line: error: …` with exit code 1.
|
|
64
68
|
|
|
69
|
+
## From a dbt project
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
dbt docs generate # optional: adds real column types (catalog.json)
|
|
73
|
+
qdbd import dbt target/manifest.json -o erd/ # one .txt per folder (staging, marts, sources, ...)
|
|
74
|
+
qdbd build erd/ -o erd.html
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **Tables:** models, seeds and snapshots of your project, plus sources.
|
|
78
|
+
- **Relationships:** `relationships` tests (including dbt_utils `relationships_where`) and `foreign_key` constraints.
|
|
79
|
+
- **Primary keys:** `primary_key` constraints, or else a column with both `unique` and `not_null` tests.
|
|
80
|
+
- **Docs:** model and column descriptions become doc comments, shown as tooltips.
|
|
81
|
+
- **Options:**
|
|
82
|
+
- `--select 'marts.*'` narrows by name, fqn or path (repeatable)
|
|
83
|
+
- `--package all` includes installed packages
|
|
84
|
+
- `--no-sources` drops sources
|
|
85
|
+
- `-o schema.txt` writes a single file
|
|
86
|
+
|
|
87
|
+
Re-running the import overwrites the generated files. Table positions survive because they live in the layout file, keyed by table name.
|
|
88
|
+
|
|
89
|
+
## Selecting tables
|
|
90
|
+
|
|
91
|
+
Like dbt's `--select`, but it follows relationships instead of lineage. It works in the page's filter box and as `-s/--select` / `--exclude` on `build` and `export`.
|
|
92
|
+
|
|
93
|
+
| Selector | Tables |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `orders` / `stg_*` | by name or alias (glob, case-insensitive) |
|
|
96
|
+
| `+orders` | `orders` and every table it references (any depth); `1+orders` limits to one hop |
|
|
97
|
+
| `orders+` | `orders` and every table that references it; `orders+2` limits to two hops |
|
|
98
|
+
| `+orders+1` | both directions |
|
|
99
|
+
| `group:marts` | a file or dbt folder (and its subfolders) |
|
|
100
|
+
| `file:core.txt` | a schema file (glob) |
|
|
101
|
+
| `col:customer_id` | tables with a matching column |
|
|
102
|
+
| `diff:modified` / `diff:added` / `diff:changed` | changed since `--diff-base` (like dbt `state:modified`) |
|
|
103
|
+
| `a b` / `a,b` / `!a` | union / intersection / exclude |
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
qdbd build erd/ -s 'group:marts' --exclude '*_tmp' -o marts.html
|
|
107
|
+
qdbd export erd/ -s '+orders+1' -o orders.png
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Schema diffs
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
qdbd diff old.txt new.txt # two versions
|
|
114
|
+
qdbd diff --base origin/main erd/ # git ref vs working tree
|
|
115
|
+
qdbd diff --base origin/main erd/ --format markdown -o diff.md # for a PR comment
|
|
116
|
+
qdbd build erd/ --diff-base origin/main -o erd.html # colour what changed
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`diff` reports tables added and removed, column changes (added, removed, type and flag changes) and relationships added and removed. `--format json` is available, and `--exit-code` exits 1 when anything changed.
|
|
120
|
+
|
|
121
|
+
With `--diff-base`, the page:
|
|
122
|
+
- marks new tables green and changed tables amber, with a bar on each changed column;
|
|
123
|
+
- shows a summary banner, and its **Show changes** link filters to the changes plus their neighbours;
|
|
124
|
+
- lists each table's changes in the detail panel.
|
|
125
|
+
|
|
126
|
+
The selector `diff:modified` (or `diff:added` / `diff:changed`) works like dbt's `state:modified`, e.g. `-s 'diff:modified+1'`.
|
|
127
|
+
|
|
65
128
|
## Commands
|
|
66
129
|
|
|
67
130
|
```bash
|
|
68
131
|
qdbd check PATH... [--strict] # validate; "No errors found. Good job!"
|
|
69
132
|
qdbd build PATH... -o diagram.html # shareable interactive HTML (positions locked)
|
|
70
133
|
qdbd serve PATH... [--port 8765] # live editor + drag-to-arrange
|
|
71
|
-
qdbd export PATH... -o out.png [--focus Customer --depth 1] # PNG or
|
|
134
|
+
qdbd export PATH... -o out.png [--focus Customer --depth 1] # PNG, SVG or .drawio
|
|
72
135
|
qdbd layout PATH... [--reset] # (re)write the layout file
|
|
136
|
+
qdbd import dbt target/manifest.json -o erd/ # generate schema files from a dbt project
|
|
73
137
|
```
|
|
74
138
|
|
|
75
139
|
`PATH` is a `.txt` file or a folder. Every `*.txt` under a folder is read recursively.
|
|
@@ -102,27 +166,45 @@ Once a layout exists, a new table is placed next to the tables it's related to,
|
|
|
102
166
|
|
|
103
167
|
## CI
|
|
104
168
|
|
|
169
|
+
**GitHub Action:** checks the schema and builds the diagram. On pull requests it also posts one schema-diff comment, updated on every push, and highlights the changes in the diagram artifact.
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
name: ERD
|
|
173
|
+
on: [pull_request, push]
|
|
174
|
+
jobs:
|
|
175
|
+
erd:
|
|
176
|
+
runs-on: ubuntu-latest
|
|
177
|
+
permissions: { contents: read, pull-requests: write }
|
|
178
|
+
steps:
|
|
179
|
+
- uses: actions/checkout@v4
|
|
180
|
+
- uses: MarkPhamm/quickdbd-cli@v0.1.4
|
|
181
|
+
with:
|
|
182
|
+
path: erd/ # schema file(s) or folder(s)
|
|
183
|
+
output: site/index.html
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Input | Default | |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `path` | (required) | schema files or folders |
|
|
189
|
+
| `output` | `erd/index.html` | HTML diagram path |
|
|
190
|
+
| `check` / `strict` | `true` / `false` | fail on errors / also on QuickDBD-compatibility warnings |
|
|
191
|
+
| `comment` | `true` | post the diff comment on PRs |
|
|
192
|
+
| `select` | | dbt-style selector for the diagram |
|
|
193
|
+
| `qdbd-version` | `latest` | PyPI version |
|
|
194
|
+
|
|
195
|
+
Outputs are `html`, `changed` and `diff-file`.
|
|
196
|
+
|
|
197
|
+
**Publishing:** to publish the diagram on GitHub Pages, add `actions/upload-pages-artifact` + `actions/deploy-pages` after it. See [`.github/workflows/erd.yml`](.github/workflows/erd.yml), which publishes this repo's demo.
|
|
198
|
+
|
|
105
199
|
**pre-commit:**
|
|
106
200
|
|
|
107
201
|
```yaml
|
|
108
202
|
repos:
|
|
109
203
|
- repo: https://github.com/MarkPhamm/quickdbd-cli
|
|
110
|
-
rev: v0.1.
|
|
204
|
+
rev: v0.1.4
|
|
111
205
|
hooks:
|
|
112
206
|
- id: qdbd-check
|
|
113
|
-
files: ^
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**GitHub Actions:**
|
|
117
|
-
|
|
118
|
-
```yaml
|
|
119
|
-
- uses: actions/setup-python@v5
|
|
120
|
-
with: { python-version: "3.12" }
|
|
121
|
-
- run: pip install quickdbd-cli
|
|
122
|
-
- run: qdbd check schema/
|
|
123
|
-
- run: qdbd build schema/ -o site/schema.html
|
|
124
|
-
- uses: actions/upload-artifact@v4
|
|
125
|
-
with: { name: schema-diagram, path: site/schema.html }
|
|
207
|
+
files: ^erd/.*\.txt$ # point at your schema files
|
|
126
208
|
```
|
|
127
209
|
|
|
128
210
|
**GitLab CI:**
|
|
@@ -132,10 +214,10 @@ erd:
|
|
|
132
214
|
image: python:3.12
|
|
133
215
|
script:
|
|
134
216
|
- pip install quickdbd-cli
|
|
135
|
-
- qdbd check
|
|
136
|
-
- qdbd build
|
|
217
|
+
- qdbd check erd/
|
|
218
|
+
- qdbd build erd/ -o public/index.html
|
|
137
219
|
artifacts:
|
|
138
|
-
paths: [public/
|
|
220
|
+
paths: [public/index.html]
|
|
139
221
|
```
|
|
140
222
|
|
|
141
223
|
## Development
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# quickdbd-cli (`qdbd`)
|
|
2
|
+
|
|
3
|
+
Compile [QuickDBD](https://www.quickdatabasediagrams.com)-syntax schema files into **interactive HTML ER diagrams**, with no table limit, from the command line. Built to stay readable at 200+ tables.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Customer
|
|
7
|
+
-
|
|
8
|
+
CustomerId PK int
|
|
9
|
+
Name string
|
|
10
|
+
|
|
11
|
+
Order
|
|
12
|
+
-
|
|
13
|
+
OrderId PK int
|
|
14
|
+
CustomerId int FK >- Customer.CustomerId
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pipx install quickdbd-cli # or: uvx --from quickdbd-cli qdbd ...
|
|
19
|
+
qdbd build schema.txt -o schema.html
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Live demo (200 tables):** https://markphamm.github.io/quickdbd-cli/
|
|
23
|
+
|
|
24
|
+
Not affiliated with QuickDBD. It reads the same text syntax, so schemas move freely between the two.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Same syntax as QuickDBD:** tables, aliases, quoted names, all 9 relationship operators, `rel` lines, PK/FK/NULL/UNIQUE/INDEX/IDENTITY, `default=`, doc comments, `[settings]` and `[layout]` sections. Error messages are QuickDBD's own.
|
|
29
|
+
- **Interactive page:**
|
|
30
|
+
- explorer sidebar: tables grouped by file or dbt folder, with search (`/`, Enter jumps to the best match)
|
|
31
|
+
- table detail panel (dbt-docs style, for relationships): description, columns with type/keys/docs, and clickable **References** / **Referenced by** lists
|
|
32
|
+
- minimap
|
|
33
|
+
- click a table to focus on it and its neighbours
|
|
34
|
+
- collapse tables: full / keys only / names only
|
|
35
|
+
- hover a column to highlight its relationship
|
|
36
|
+
- hover a dotted-underlined table or column to read its `#` doc comments
|
|
37
|
+
- PNG/SVG export buttons, and **draw.io**: an editable diagrams.net file using draw.io's ER table shapes and crow's-foot arrows
|
|
38
|
+
- shareable links: the URL keeps the filter, selected table, collapse mode and zoom; **Link** copies it
|
|
39
|
+
- **Live editing:** `qdbd serve` reloads when you save, has a browser editor pane with autosave, and remembers where you drag tables.
|
|
40
|
+
- **Many files, one diagram:** point it at a folder and foreign keys can cross files.
|
|
41
|
+
- **Pip-only install:** no Graphviz, Node or browser needed. Layout uses [ELK](https://eclipse.dev/elk/), running in an embedded V8.
|
|
42
|
+
- **CI-friendly:** builds are deterministic, and errors are `file:line: error: …` with exit code 1.
|
|
43
|
+
|
|
44
|
+
## From a dbt project
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
dbt docs generate # optional: adds real column types (catalog.json)
|
|
48
|
+
qdbd import dbt target/manifest.json -o erd/ # one .txt per folder (staging, marts, sources, ...)
|
|
49
|
+
qdbd build erd/ -o erd.html
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Tables:** models, seeds and snapshots of your project, plus sources.
|
|
53
|
+
- **Relationships:** `relationships` tests (including dbt_utils `relationships_where`) and `foreign_key` constraints.
|
|
54
|
+
- **Primary keys:** `primary_key` constraints, or else a column with both `unique` and `not_null` tests.
|
|
55
|
+
- **Docs:** model and column descriptions become doc comments, shown as tooltips.
|
|
56
|
+
- **Options:**
|
|
57
|
+
- `--select 'marts.*'` narrows by name, fqn or path (repeatable)
|
|
58
|
+
- `--package all` includes installed packages
|
|
59
|
+
- `--no-sources` drops sources
|
|
60
|
+
- `-o schema.txt` writes a single file
|
|
61
|
+
|
|
62
|
+
Re-running the import overwrites the generated files. Table positions survive because they live in the layout file, keyed by table name.
|
|
63
|
+
|
|
64
|
+
## Selecting tables
|
|
65
|
+
|
|
66
|
+
Like dbt's `--select`, but it follows relationships instead of lineage. It works in the page's filter box and as `-s/--select` / `--exclude` on `build` and `export`.
|
|
67
|
+
|
|
68
|
+
| Selector | Tables |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `orders` / `stg_*` | by name or alias (glob, case-insensitive) |
|
|
71
|
+
| `+orders` | `orders` and every table it references (any depth); `1+orders` limits to one hop |
|
|
72
|
+
| `orders+` | `orders` and every table that references it; `orders+2` limits to two hops |
|
|
73
|
+
| `+orders+1` | both directions |
|
|
74
|
+
| `group:marts` | a file or dbt folder (and its subfolders) |
|
|
75
|
+
| `file:core.txt` | a schema file (glob) |
|
|
76
|
+
| `col:customer_id` | tables with a matching column |
|
|
77
|
+
| `diff:modified` / `diff:added` / `diff:changed` | changed since `--diff-base` (like dbt `state:modified`) |
|
|
78
|
+
| `a b` / `a,b` / `!a` | union / intersection / exclude |
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
qdbd build erd/ -s 'group:marts' --exclude '*_tmp' -o marts.html
|
|
82
|
+
qdbd export erd/ -s '+orders+1' -o orders.png
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Schema diffs
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
qdbd diff old.txt new.txt # two versions
|
|
89
|
+
qdbd diff --base origin/main erd/ # git ref vs working tree
|
|
90
|
+
qdbd diff --base origin/main erd/ --format markdown -o diff.md # for a PR comment
|
|
91
|
+
qdbd build erd/ --diff-base origin/main -o erd.html # colour what changed
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`diff` reports tables added and removed, column changes (added, removed, type and flag changes) and relationships added and removed. `--format json` is available, and `--exit-code` exits 1 when anything changed.
|
|
95
|
+
|
|
96
|
+
With `--diff-base`, the page:
|
|
97
|
+
- marks new tables green and changed tables amber, with a bar on each changed column;
|
|
98
|
+
- shows a summary banner, and its **Show changes** link filters to the changes plus their neighbours;
|
|
99
|
+
- lists each table's changes in the detail panel.
|
|
100
|
+
|
|
101
|
+
The selector `diff:modified` (or `diff:added` / `diff:changed`) works like dbt's `state:modified`, e.g. `-s 'diff:modified+1'`.
|
|
102
|
+
|
|
103
|
+
## Commands
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
qdbd check PATH... [--strict] # validate; "No errors found. Good job!"
|
|
107
|
+
qdbd build PATH... -o diagram.html # shareable interactive HTML (positions locked)
|
|
108
|
+
qdbd serve PATH... [--port 8765] # live editor + drag-to-arrange
|
|
109
|
+
qdbd export PATH... -o out.png [--focus Customer --depth 1] # PNG, SVG or .drawio
|
|
110
|
+
qdbd layout PATH... [--reset] # (re)write the layout file
|
|
111
|
+
qdbd import dbt target/manifest.json -o erd/ # generate schema files from a dbt project
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`PATH` is a `.txt` file or a folder. Every `*.txt` under a folder is read recursively.
|
|
115
|
+
|
|
116
|
+
### Table positions
|
|
117
|
+
|
|
118
|
+
The first build auto-arranges the tables. In `qdbd serve`, drag tables where you want them. Positions are saved to a sidecar file you can commit:
|
|
119
|
+
|
|
120
|
+
- `schema.txt` → `schema.layout.json`
|
|
121
|
+
- a folder → `<folder>/qdbd.layout.json`
|
|
122
|
+
- override either with `--layout FILE`
|
|
123
|
+
|
|
124
|
+
Once a layout exists, a new table is placed next to the tables it's related to, and nothing else moves. Run `qdbd layout PATH --reset` to re-arrange everything from scratch. A pasted schema's QuickDBD `[layout]` section is used for any table the sidecar doesn't cover.
|
|
125
|
+
|
|
126
|
+
`qdbd build` never writes the sidecar, so CI builds leave the working tree clean.
|
|
127
|
+
|
|
128
|
+
### Differences from QuickDBD
|
|
129
|
+
|
|
130
|
+
`qdbd` accepts a few things QuickDBD rejects, and prints a warning for each one:
|
|
131
|
+
|
|
132
|
+
- tab characters
|
|
133
|
+
- `decimal(10, 2)` (a space inside a type)
|
|
134
|
+
- an explicit `NOT NULL`
|
|
135
|
+
- indented table names
|
|
136
|
+
- `[layout]` entries for unknown tables
|
|
137
|
+
|
|
138
|
+
`qdbd check --strict` turns these warnings into errors, so a file that passes is guaranteed to paste into QuickDBD.
|
|
139
|
+
|
|
140
|
+
`default=` values keep their case, where QuickDBD lowercases them.
|
|
141
|
+
|
|
142
|
+
## CI
|
|
143
|
+
|
|
144
|
+
**GitHub Action:** checks the schema and builds the diagram. On pull requests it also posts one schema-diff comment, updated on every push, and highlights the changes in the diagram artifact.
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
name: ERD
|
|
148
|
+
on: [pull_request, push]
|
|
149
|
+
jobs:
|
|
150
|
+
erd:
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
permissions: { contents: read, pull-requests: write }
|
|
153
|
+
steps:
|
|
154
|
+
- uses: actions/checkout@v4
|
|
155
|
+
- uses: MarkPhamm/quickdbd-cli@v0.1.4
|
|
156
|
+
with:
|
|
157
|
+
path: erd/ # schema file(s) or folder(s)
|
|
158
|
+
output: site/index.html
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Input | Default | |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `path` | (required) | schema files or folders |
|
|
164
|
+
| `output` | `erd/index.html` | HTML diagram path |
|
|
165
|
+
| `check` / `strict` | `true` / `false` | fail on errors / also on QuickDBD-compatibility warnings |
|
|
166
|
+
| `comment` | `true` | post the diff comment on PRs |
|
|
167
|
+
| `select` | | dbt-style selector for the diagram |
|
|
168
|
+
| `qdbd-version` | `latest` | PyPI version |
|
|
169
|
+
|
|
170
|
+
Outputs are `html`, `changed` and `diff-file`.
|
|
171
|
+
|
|
172
|
+
**Publishing:** to publish the diagram on GitHub Pages, add `actions/upload-pages-artifact` + `actions/deploy-pages` after it. See [`.github/workflows/erd.yml`](.github/workflows/erd.yml), which publishes this repo's demo.
|
|
173
|
+
|
|
174
|
+
**pre-commit:**
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
repos:
|
|
178
|
+
- repo: https://github.com/MarkPhamm/quickdbd-cli
|
|
179
|
+
rev: v0.1.4
|
|
180
|
+
hooks:
|
|
181
|
+
- id: qdbd-check
|
|
182
|
+
files: ^erd/.*\.txt$ # point at your schema files
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**GitLab CI:**
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
erd:
|
|
189
|
+
image: python:3.12
|
|
190
|
+
script:
|
|
191
|
+
- pip install quickdbd-cli
|
|
192
|
+
- qdbd check erd/
|
|
193
|
+
- qdbd build erd/ -o public/index.html
|
|
194
|
+
artifacts:
|
|
195
|
+
paths: [public/index.html]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
uv venv && uv pip install -e ".[dev]"
|
|
202
|
+
.venv/bin/pytest
|
|
203
|
+
python scripts/gen_schema.py # regenerate examples/synthetic (200 tables)
|
|
204
|
+
python scripts/gen_font_metrics.py # regenerate font width table
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Design notes: `docs/superpowers/specs/2026-10-17-qdbd-design.md`.
|
|
208
|
+
|
|
209
|
+
### Releasing
|
|
210
|
+
|
|
211
|
+
Put PyPI credentials in `.env` (gitignored):
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
TWINE_USERNAME=__token__
|
|
215
|
+
TWINE_PASSWORD=pypi-...
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Bump `version` in `pyproject.toml` and `src/quickdbd_cli/__init__.py`, commit, then run `PATH="$PWD/.venv/bin:$PATH" scripts/release.sh`. It builds, uploads to PyPI, tags `vX.Y.Z` and creates the GitHub release.
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
Apache-2.0. Bundled third-party files are listed in [NOTICE](NOTICE).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quickdbd-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.4"
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
badgeBg: "#eef1f5",
|
|
19
19
|
badgeText: "#7a8899",
|
|
20
20
|
edge: "#b8bcc2",
|
|
21
|
+
added: "#2e7d32",
|
|
22
|
+
changed: "#d48806",
|
|
21
23
|
};
|
|
22
24
|
var FONT = "Inter, 'Helvetica Neue', Helvetica, Arial, sans-serif";
|
|
23
25
|
|
|
@@ -76,7 +78,12 @@
|
|
|
76
78
|
var st = model.style, w = g.w, h = g.h, out = [];
|
|
77
79
|
out.push('<g class="t" data-id="' + esc(t.id) + '" transform="translate(' + n(p[0]) + "," + n(p[1]) + ')">');
|
|
78
80
|
out.push('<rect class="tb" width="' + w + '" height="' + h + '" rx="2" fill="' + C.body + '" stroke="' + C.border + '"/>');
|
|
79
|
-
|
|
81
|
+
var state = t.diff && t.diff.state, hcol = state === "added" ? C.added : state === "changed" ? C.changed : C.header;
|
|
82
|
+
out.push('<path class="th" d="M0 2a2 2 0 0 1 2-2h' + (w - 4) + "a2 2 0 0 1 2 2v" + (st.headerH - 2) + "h-" + w + 'z" fill="' + hcol + '"/>');
|
|
83
|
+
if (state) {
|
|
84
|
+
out.push('<text x="' + (w - st.padX) + '" y="' + n(st.headerH / 2 + 4) + '" text-anchor="end" fill="#fff" font-family="' + FONT +
|
|
85
|
+
'" font-size="10" font-weight="bold">' + (state === "added" ? "NEW" : "CHANGED") + "</text>");
|
|
86
|
+
}
|
|
80
87
|
out.push('<text x="' + st.padX + '" y="' + n(st.headerH / 2 + st.headerSize * 0.36) + '" fill="' + C.headerText +
|
|
81
88
|
'" font-family="' + FONT + '" font-size="' + st.headerSize + '">' + esc(t.name) + "</text>");
|
|
82
89
|
if (docMarks && t.doc) {
|
|
@@ -88,6 +95,8 @@
|
|
|
88
95
|
var color = c.pk ? C.pk : c.fk ? C.fk : C.text;
|
|
89
96
|
out.push('<g class="r" data-c="' + esc(c.id) + '">');
|
|
90
97
|
out.push('<rect class="rb" y="' + y + '" width="' + w + '" height="' + st.rowH + '" fill="' + C.body + '" fill-opacity="0"/>');
|
|
98
|
+
var cstate = t.diff && t.diff.cols && t.diff.cols[c.id];
|
|
99
|
+
if (cstate) out.push('<rect y="' + (y + 3) + '" width="3" height="' + (st.rowH - 6) + '" fill="' + C[cstate] + '"/>');
|
|
91
100
|
out.push('<text x="' + st.padX + '" y="' + base + '" fill="' + color + '" font-family="' + FONT + '" font-size="' +
|
|
92
101
|
st.rowSize + '"' + weight + ">" + esc(c.name) + "</text>");
|
|
93
102
|
if (docMarks && c.doc) out.push(docMark(st.padX, cy + st.rowSize * 0.36 + 2.5, c.nameW, color));
|
|
@@ -99,7 +108,7 @@
|
|
|
99
108
|
'" font-family="' + FONT + '" font-size="' + st.badgeSize + '">' + esc(b.t) + "</text>");
|
|
100
109
|
bx += b.w + st.badgeGap;
|
|
101
110
|
});
|
|
102
|
-
if (c.pk) out.push(keyIcon(w - st.padX - c.typeW - st.keyW
|
|
111
|
+
if (c.pk) out.push(keyIcon(w - st.padX - c.typeW - st.keyW, cy));
|
|
103
112
|
out.push('<text x="' + (w - st.padX) + '" y="' + base + '" text-anchor="end" fill="' + C.type + '" font-family="' + FONT +
|
|
104
113
|
'" font-size="' + st.rowSize + '">' + esc(c.type) + "</text>");
|
|
105
114
|
out.push("</g>");
|
|
@@ -225,7 +234,153 @@
|
|
|
225
234
|
return out;
|
|
226
235
|
}
|
|
227
236
|
|
|
237
|
+
/* ------------------------------------------------------------------ draw.io export
|
|
238
|
+
* mxGraph XML using draw.io's own Entity Relation table shapes, so tables stay editable
|
|
239
|
+
* (rows can be added, lines re-attached) and crow's-foot arrows are draw.io's ER arrows. */
|
|
240
|
+
var DRAWIO_ARROW = { one: "ERmandOne", many: "ERmany", zeroOne: "ERzeroToOne", zeroMany: "ERzeroToMany" };
|
|
241
|
+
function renderDrawio(model, pos, opts) {
|
|
242
|
+
opts = opts || {};
|
|
243
|
+
var mode = opts.mode || "full", m = subset(model, opts.only), geo = geometry(m, mode);
|
|
244
|
+
var KEYW = 44, ROWH = 26, HEADH = 32, cells = [], rowId = {}, n = 2;
|
|
245
|
+
function id() { return "q" + n++; }
|
|
246
|
+
function cell(attrs, geom) {
|
|
247
|
+
var a = Object.keys(attrs).map(function (k) { return k + '="' + esc(attrs[k]) + '"'; }).join(" ");
|
|
248
|
+
return "<mxCell " + a + ">" + geom + "</mxCell>";
|
|
249
|
+
}
|
|
250
|
+
function g(x, y, w, h) {
|
|
251
|
+
return '<mxGeometry x="' + n2(x) + '" y="' + n2(y) + '" width="' + n2(w) + '" height="' + n2(h) + '" as="geometry"/>';
|
|
252
|
+
}
|
|
253
|
+
function n2(v) { return Math.round(v); }
|
|
254
|
+
var b = bounds(m, pos, mode);
|
|
255
|
+
m.tables.forEach(function (t) {
|
|
256
|
+
var p = pos[t.id];
|
|
257
|
+
if (!p) return;
|
|
258
|
+
var tg = geo[t.id], w = tg.w + KEYW, tid = id();
|
|
259
|
+
rowId[t.id] = { _table: tid };
|
|
260
|
+
cells.push(cell({
|
|
261
|
+
id: tid, value: t.name, vertex: "1", parent: "1",
|
|
262
|
+
style: "shape=table;startSize=" + HEADH + ";container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;" +
|
|
263
|
+
"fontStyle=1;align=left;spacingLeft=10;resizeLast=1;html=0;whiteSpace=wrap;fillColor=#2b5797;fontColor=#ffffff;" +
|
|
264
|
+
"strokeColor=#dfe3e8;swimlaneFillColor=#ffffff;fontSize=14;",
|
|
265
|
+
}, g(p[0] - b[0] + 40, p[1] - b[1] + 40, w, HEADH + Math.max(tg.cols.length, 0) * ROWH)));
|
|
266
|
+
tg.cols.forEach(function (c, i) {
|
|
267
|
+
var rid = id(), key = c.pk && c.fk ? "PK,FK" : c.pk ? "PK" : c.fk ? "FK" : "";
|
|
268
|
+
rowId[t.id][c.id] = rid;
|
|
269
|
+
cells.push(cell({
|
|
270
|
+
id: rid, value: "", vertex: "1", parent: tid,
|
|
271
|
+
style: "shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;" +
|
|
272
|
+
"dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;",
|
|
273
|
+
}, g(0, HEADH + i * ROWH, w, ROWH)));
|
|
274
|
+
var base = "shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;overflow=hidden;html=0;";
|
|
275
|
+
var flags = c.badges.map(function (x) { return x.t; }).join(" ");
|
|
276
|
+
cells.push(cell({ id: id(), value: key, vertex: "1", parent: rid, style: base + "fontStyle=1;fontColor=#2b5797;align=center;fontSize=11;" },
|
|
277
|
+
g(0, 0, KEYW, ROWH)));
|
|
278
|
+
cells.push(cell({
|
|
279
|
+
id: id(), value: c.name + (flags ? " [" + flags + "]" : "") + (c.type ? " : " + c.type : ""), vertex: "1", parent: rid,
|
|
280
|
+
style: base + "align=left;spacingLeft=6;fontSize=12;" + (c.pk ? "fontStyle=5;" : c.fk ? "fontStyle=1;" : ""),
|
|
281
|
+
}, g(KEYW, 0, w - KEYW, ROWH)));
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
m.rels.forEach(function (r) {
|
|
285
|
+
var ra = rowId[r.a.t], rb = rowId[r.b.t];
|
|
286
|
+
if (!ra || !rb) return;
|
|
287
|
+
var src = ra[r.a.c] || ra._table, dst = rb[r.b.c] || rb._table;
|
|
288
|
+
cells.push(cell({
|
|
289
|
+
id: id(), value: "", edge: "1", parent: "1", source: src, target: dst,
|
|
290
|
+
style: "edgeStyle=entityRelationEdgeStyle;fontSize=12;html=0;endFill=0;startFill=0;strokeColor=#9aa0a6;" +
|
|
291
|
+
"startArrow=" + DRAWIO_ARROW[r.a.card] + ";endArrow=" + DRAWIO_ARROW[r.b.card] + ";",
|
|
292
|
+
}, '<mxGeometry relative="1" as="geometry"/>'));
|
|
293
|
+
});
|
|
294
|
+
return '<mxfile host="qdbd" type="device"><diagram id="erd" name="' + esc(opts.title || "ERD") + '">' +
|
|
295
|
+
'<mxGraphModel dx="1200" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" ' +
|
|
296
|
+
'page="0" pageScale="1" math="0" shadow="0"><root><mxCell id="0"/><mxCell id="1" parent="0"/>' +
|
|
297
|
+
cells.join("") + "</root></mxGraphModel></diagram></mxfile>";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* ------------------------------------------------------------------ selectors (dbt-style, over relationships)
|
|
301
|
+
* Space-separated atoms are unioned; a comma inside an atom intersects (a,b); a leading ! excludes.
|
|
302
|
+
* Atom: [N]+ method:pattern +[N]
|
|
303
|
+
* method: name (default; also matches aliases), group, file, col
|
|
304
|
+
* pattern: glob with * and ?
|
|
305
|
+
* +x x plus the tables it references (parents), any depth; 2+x limits to 2 hops
|
|
306
|
+
* x+ x plus the tables referencing it (children); x+1 limits to 1 hop
|
|
307
|
+
*/
|
|
308
|
+
function globRe(p) {
|
|
309
|
+
return new RegExp("^" + p.replace(/[.^$|()[\]{}\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$", "i");
|
|
310
|
+
}
|
|
311
|
+
function edges(model) {
|
|
312
|
+
var up = {}, down = {};
|
|
313
|
+
model.rels.forEach(function (r) {
|
|
314
|
+
var aMany = r.a.card === "many" || r.a.card === "zeroMany", bMany = r.b.card === "many" || r.b.card === "zeroMany";
|
|
315
|
+
var aIsChild = aMany || !bMany, child = aIsChild ? r.a.t : r.b.t, parent = aIsChild ? r.b.t : r.a.t;
|
|
316
|
+
if (child === parent) return;
|
|
317
|
+
(up[child] = up[child] || []).push(parent);
|
|
318
|
+
(down[parent] = down[parent] || []).push(child);
|
|
319
|
+
});
|
|
320
|
+
return { up: up, down: down };
|
|
321
|
+
}
|
|
322
|
+
function walk(start, adj, depth, into) {
|
|
323
|
+
var frontier = start, d = 0;
|
|
324
|
+
while (frontier.length && d < depth) {
|
|
325
|
+
var next = [];
|
|
326
|
+
frontier.forEach(function (id) {
|
|
327
|
+
(adj[id] || []).forEach(function (o) { if (!into[o]) { into[o] = true; next.push(o); } });
|
|
328
|
+
});
|
|
329
|
+
frontier = next;
|
|
330
|
+
d++;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function atomSet(model, atom, g) {
|
|
334
|
+
var m = /^(\d*\+)?(.*?)(\+\d*)?$/.exec(atom), up = m[1], body = m[2], down = m[3];
|
|
335
|
+
var method = "name", pat = body, i = body.indexOf(":");
|
|
336
|
+
if (i > 0) { method = body.slice(0, i).toLowerCase(); pat = body.slice(i + 1); }
|
|
337
|
+
var re = globRe(pat || "*"), out = {}, base = [];
|
|
338
|
+
model.tables.forEach(function (t) {
|
|
339
|
+
var ok;
|
|
340
|
+
if (method === "name") ok = re.test(t.name) || t.aliases.some(function (a) { return re.test(a); });
|
|
341
|
+
else if (method === "group") ok = re.test(t.group || "") || (t.group || "").toLowerCase().indexOf(pat.toLowerCase() + "/") === 0;
|
|
342
|
+
else if (method === "file") ok = re.test(t.file || "") || re.test((t.file || "").split("/").pop());
|
|
343
|
+
else if (method === "col" || method === "column") ok = t.cols.some(function (c) { return re.test(c.name); });
|
|
344
|
+
else if (method === "diff" || method === "state") {
|
|
345
|
+
var ds = t.diff && t.diff.state, want = pat.toLowerCase();
|
|
346
|
+
ok = !!ds && (want === "modified" || want === "*" || want === ds || (want === "new" && ds === "added"));
|
|
347
|
+
}
|
|
348
|
+
else throw new Error("unknown selector method '" + method + "' (use name, group, file, col, diff)");
|
|
349
|
+
if (ok) { out[t.id] = true; base.push(t.id); }
|
|
350
|
+
});
|
|
351
|
+
function hops(op, before) {
|
|
352
|
+
var n = before ? op.slice(0, -1) : op.slice(1);
|
|
353
|
+
return n ? parseInt(n, 10) : Infinity;
|
|
354
|
+
}
|
|
355
|
+
if (up) walk(base, g.up, hops(up, true), out);
|
|
356
|
+
if (down) walk(base, g.down, hops(down, false), out);
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
/* Returns the ids matching `expr`, in model order, or null when expr is blank (= everything). */
|
|
360
|
+
function select(model, expr) {
|
|
361
|
+
var tokens = String(expr || "").trim().split(/\s+/).filter(Boolean);
|
|
362
|
+
if (!tokens.length) return null;
|
|
363
|
+
var g = edges(model), inc = null, exc = {};
|
|
364
|
+
tokens.forEach(function (tok) {
|
|
365
|
+
var neg = tok.charAt(0) === "!";
|
|
366
|
+
if (neg) tok = tok.slice(1);
|
|
367
|
+
var parts = tok.split(",").filter(Boolean), set = null;
|
|
368
|
+
parts.forEach(function (p) {
|
|
369
|
+
var s = atomSet(model, p, g);
|
|
370
|
+
if (set === null) set = s;
|
|
371
|
+
else { var k = {}; Object.keys(set).forEach(function (id) { if (s[id]) k[id] = true; }); set = k; }
|
|
372
|
+
});
|
|
373
|
+
set = set || {};
|
|
374
|
+
if (neg) Object.keys(set).forEach(function (id) { exc[id] = true; });
|
|
375
|
+
else { inc = inc || {}; Object.keys(set).forEach(function (id) { inc[id] = true; }); }
|
|
376
|
+
});
|
|
377
|
+
return model.tables.filter(function (t) { return (inc === null || inc[t.id]) && !exc[t.id]; }).map(function (t) { return t.id; });
|
|
378
|
+
}
|
|
379
|
+
|
|
228
380
|
root.qdbd = {
|
|
381
|
+
renderDrawio: renderDrawio,
|
|
382
|
+
select: select,
|
|
383
|
+
subset: subset,
|
|
229
384
|
colors: C,
|
|
230
385
|
geometry: geometry,
|
|
231
386
|
renderTable: renderTable,
|
|
@@ -237,5 +392,7 @@
|
|
|
237
392
|
esc: esc,
|
|
238
393
|
};
|
|
239
394
|
root.qdbdRenderSvg = renderSvg;
|
|
395
|
+
root.qdbdSelect = select;
|
|
396
|
+
root.qdbdRenderDrawio = renderDrawio;
|
|
240
397
|
root.qdbdNeighbourhood = neighbourhood;
|
|
241
398
|
})(typeof window !== "undefined" ? window : globalThis);
|