modscape 2.0.4 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +183 -3
- package/README.md +183 -3
- package/package.json +1 -1
- package/src/layout.js +47 -26
- package/src/templates/rules.md +75 -11
- package/visualizer/package.json +1 -1
- package/visualizer-dist/assets/index-CVn9qVGW.css +1 -0
- package/visualizer-dist/assets/index-Crci4Usl.js +432 -0
- package/visualizer-dist/index.html +2 -2
- package/visualizer-dist/assets/index-FOHZfUSs.js +0 -432
- package/visualizer-dist/assets/index-zFK5TNtm.css +0 -1
package/README.ja.md
CHANGED
|
@@ -99,6 +99,7 @@ relationships – テーブル間のERカーディナリティ
|
|
|
99
99
|
lineage – データの流れ / 変換パス
|
|
100
100
|
annotations – キャンバス上のスティッキーノート・吹き出し
|
|
101
101
|
layout – 全座標データ(tables/domains の中に x/y を書いてはいけない)
|
|
102
|
+
consumers – データの下流消費者(BIダッシュボード・MLモデル・アプリケーション等)
|
|
102
103
|
```
|
|
103
104
|
|
|
104
105
|
### Domains(ドメイン)
|
|
@@ -109,8 +110,7 @@ domains:
|
|
|
109
110
|
name: "主要売上"
|
|
110
111
|
description: "営業チームのトランザクションデータ。" # 任意
|
|
111
112
|
color: "rgba(59, 130, 246, 0.1)" # 背景色
|
|
112
|
-
|
|
113
|
-
isLocked: false # true にするとキャンバスでの誤ドラッグを防止
|
|
113
|
+
members: [orders, dim_customers] # 論理的な所属リスト
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
### Tables(テーブル)
|
|
@@ -197,6 +197,31 @@ relationships:
|
|
|
197
197
|
|
|
198
198
|
> **ER関係** vs **リネージ**: 構造的な結合(外部キーなど)には `relationships` を、データの加工・変換の流れには `lineage` を使用してください。両方に同じ接続を記述しないでください。
|
|
199
199
|
|
|
200
|
+
### Consumers(コンシューマー)
|
|
201
|
+
|
|
202
|
+
コンシューマーはデータモデルの下流消費者を表します。BIダッシュボード、MLモデル、アプリケーションなど、データを利用するあらゆるシステムを定義できます。キャンバス上に独自のノードとして表示され、リネージ矢印で接続されます。
|
|
203
|
+
|
|
204
|
+
```yaml
|
|
205
|
+
consumers:
|
|
206
|
+
- id: revenue_dashboard # 一意のID — lineageやlayoutで使用
|
|
207
|
+
name: "Revenue Dashboard" # 表示名
|
|
208
|
+
description: "財務チーム向け月次KPIダッシュボード" # 任意
|
|
209
|
+
appearance:
|
|
210
|
+
icon: "📊" # 任意(デフォルト: 📊)
|
|
211
|
+
color: "#e0f2fe" # 任意のアクセントカラー
|
|
212
|
+
url: "https://bi.example.com/revenue" # 任意のリンク
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
コンシューマーへのリネージは `lineage.to` にコンシューマーIDを指定します:
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
lineage:
|
|
219
|
+
- from: mart_monthly_revenue
|
|
220
|
+
to: revenue_dashboard # コンシューマーID
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
テーブルと同様に、ドメインの `members` リストにも追加できます。
|
|
224
|
+
|
|
200
225
|
### Annotations(アノテーション)
|
|
201
226
|
|
|
202
227
|
```yaml
|
|
@@ -224,7 +249,6 @@ layout:
|
|
|
224
249
|
y: 0
|
|
225
250
|
width: 880
|
|
226
251
|
height: 480
|
|
227
|
-
isLocked: false # true でキャンバスのドラッグを防止
|
|
228
252
|
|
|
229
253
|
# ドメイン内のテーブル – 座標はドメインの原点からの相対値
|
|
230
254
|
orders:
|
|
@@ -265,6 +289,162 @@ modscape build ./models -o docs-site
|
|
|
265
289
|
modscape export ./models -o docs/ARCHITECTURE.md
|
|
266
290
|
```
|
|
267
291
|
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## dbt連携
|
|
295
|
+
|
|
296
|
+
既存のdbtプロジェクトを `manifest.json` から直接インポートできます。
|
|
297
|
+
|
|
298
|
+
### 事前準備
|
|
299
|
+
|
|
300
|
+
コマンドを実行する前に、dbtプロジェクトで `dbt parse`(または `target/manifest.json` を生成する任意のdbtコマンド)を実行してください。
|
|
301
|
+
|
|
302
|
+
### dbtプロジェクトのインポート
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
modscape dbt import [project-dir] [オプション]
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| オプション | 説明 |
|
|
309
|
+
|-----------|------|
|
|
310
|
+
| `-o, --output <dir>` | 出力ディレクトリ(デフォルト: `modscape-<プロジェクト名>`) |
|
|
311
|
+
| `--split-by <key>` | `schema`、`tag`、`folder` のいずれかでYAMLファイルを分割 |
|
|
312
|
+
|
|
313
|
+
**使用例:**
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# カレントディレクトリからインポート
|
|
317
|
+
modscape dbt import
|
|
318
|
+
|
|
319
|
+
# 特定のdbtプロジェクトパスを指定
|
|
320
|
+
modscape dbt import ./my_dbt_project
|
|
321
|
+
|
|
322
|
+
# スキーマ別にYAMLファイルを分割して出力
|
|
323
|
+
modscape dbt import --split-by schema
|
|
324
|
+
|
|
325
|
+
# dbtタグ別に分割し、出力先ディレクトリを指定
|
|
326
|
+
modscape dbt import --split-by tag -o ./modscape-models
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
インポート後は以下でビジュアライザーを起動できます:
|
|
330
|
+
```bash
|
|
331
|
+
modscape dev modscape-my_project
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
> **インポートされる内容:** `manifest.json` 内の `model`、`seed`、`snapshot`、`source` ノード(カラム、説明文、`depends_on` によるリネージ含む)。
|
|
335
|
+
> **分割モード:** `--split-by` 指定時はグループごとに別YAMLファイルへ出力されます。自己完結率(self-contained rate)が80%未満のファイルは、クロスファイルのリネージ参照が単体では表示されないため注意してください。
|
|
336
|
+
|
|
337
|
+
### dbt変更の同期
|
|
338
|
+
|
|
339
|
+
dbtプロジェクトを更新した後、既存のModscape YAMLファイルへ差分を反映できます。手動で追加したレイアウト・外観・アノテーション・リレーションシップは保持されます。
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
modscape dbt sync [project-dir] [オプション]
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
| オプション | 説明 |
|
|
346
|
+
|-----------|------|
|
|
347
|
+
| `-o, --output <dir>` | 同期対象のModscape YAMLが置かれたディレクトリ(デフォルト: `modscape-<プロジェクト名>`) |
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
# カレントディレクトリのdbtプロジェクトを同期
|
|
351
|
+
modscape dbt sync
|
|
352
|
+
|
|
353
|
+
# パスを指定して同期
|
|
354
|
+
modscape dbt sync ./my_dbt_project -o ./modscape-models
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
> **sync と import の違い:** `import` はYAMLをゼロから生成します。`sync` は既存ファイルを更新するため、手動で加えたテーブル種別・ビジネス定義・サンプルデータなどの情報が失われません。
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## モデルファイル操作
|
|
362
|
+
|
|
363
|
+
### YAMLファイルのマージ
|
|
364
|
+
|
|
365
|
+
複数のYAMLモデルを1ファイルに統合します。テーブル/ドメインIDが重複した場合は先勝ちで処理されます。
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
modscape merge model-a.yaml model-b.yaml -o merged.yaml
|
|
369
|
+
|
|
370
|
+
# ディレクトリ内のすべてのYAMLをマージ
|
|
371
|
+
modscape merge ./models -o merged.yaml
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### テーブルの抽出
|
|
375
|
+
|
|
376
|
+
特定のテーブル(関連するリレーションシップ・リネージも含む)を新しいYAMLファイルへ切り出します。
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
modscape extract model.yaml --tables orders,dim_customers -o subset.yaml
|
|
380
|
+
|
|
381
|
+
# 複数ファイルから抽出
|
|
382
|
+
modscape extract ./models --tables fct_sales,dim_dates -o extracted.yaml
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### 自動レイアウト
|
|
386
|
+
|
|
387
|
+
テーブルのリレーションシップをもとに、座標を自動計算してYAMLに書き込みます。
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
modscape layout model.yaml
|
|
391
|
+
|
|
392
|
+
# 別ファイルに出力
|
|
393
|
+
modscape layout model.yaml -o model-with-layout.yaml
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## アトミックモデル操作コマンド
|
|
399
|
+
|
|
400
|
+
AIエージェントやスクリプトから、YAMLモデルファイルに対して精確な変更を加えるためのコマンドです。すべてのコマンドで `--json` オプションによる機械可読な出力が利用できます。
|
|
401
|
+
|
|
402
|
+
### テーブルコマンド
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
modscape table list <file> # テーブルID一覧を表示
|
|
406
|
+
modscape table get <file> --id <id> # 指定テーブルをJSONで取得
|
|
407
|
+
modscape table add <file> --data <json> # テーブルを追加
|
|
408
|
+
modscape table update <file> --id <id> --data <json> # テーブルを更新
|
|
409
|
+
modscape table remove <file> --id <id> # テーブルを削除
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### カラムコマンド
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
modscape column add <file> --table <id> --data <json>
|
|
416
|
+
modscape column update <file> --table <id> --id <col-id> --data <json>
|
|
417
|
+
modscape column remove <file> --table <id> --id <col-id>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### リレーションシップコマンド
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
modscape relationship list <file>
|
|
424
|
+
modscape relationship add <file> --data <json>
|
|
425
|
+
modscape relationship remove <file> --index <n>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### リネージコマンド
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
modscape lineage list <file>
|
|
432
|
+
modscape lineage add <file> --from <table-id> --to <table-id>
|
|
433
|
+
modscape lineage remove <file> --from <table-id> --to <table-id>
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### ドメインコマンド
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
modscape domain list <file>
|
|
440
|
+
modscape domain get <file> --id <id>
|
|
441
|
+
modscape domain add <file> --data <json>
|
|
442
|
+
modscape domain update <file> --id <id> --data <json>
|
|
443
|
+
modscape domain remove <file> --id <id>
|
|
444
|
+
modscape domain member add <file> --domain <id> --table <table-id>
|
|
445
|
+
modscape domain member remove <file> --domain <id> --table <table-id>
|
|
446
|
+
```
|
|
447
|
+
|
|
268
448
|
## クレジット
|
|
269
449
|
|
|
270
450
|
Modscape は以下の素晴らしいオープンソースプロジェクトによって支えられています:
|
package/README.md
CHANGED
|
@@ -100,6 +100,7 @@ relationships – ER cardinality between tables
|
|
|
100
100
|
lineage – data flow / transformation paths
|
|
101
101
|
annotations – sticky notes / callouts on the canvas
|
|
102
102
|
layout – ALL coordinate data (never put x/y inside tables or domains)
|
|
103
|
+
consumers – downstream consumers (BI dashboards, ML models, applications)
|
|
103
104
|
```
|
|
104
105
|
|
|
105
106
|
### Domains
|
|
@@ -110,8 +111,7 @@ domains:
|
|
|
110
111
|
name: "Core Sales"
|
|
111
112
|
description: "Transactional data for the sales team." # optional
|
|
112
113
|
color: "rgba(59, 130, 246, 0.1)" # background fill
|
|
113
|
-
|
|
114
|
-
isLocked: false # prevent accidental drag when true
|
|
114
|
+
members: [orders, dim_customers] # logical membership
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
### Tables
|
|
@@ -198,6 +198,31 @@ relationships:
|
|
|
198
198
|
|
|
199
199
|
> **ER Relationships** vs **Lineage**: Use `relationships` for structural joins (FKs) and `lineage` for data flow (transformations). Do not duplicate them.
|
|
200
200
|
|
|
201
|
+
### Consumers
|
|
202
|
+
|
|
203
|
+
Consumers represent the downstream users of your data model — BI dashboards, ML models, applications, or any other system that consumes the data. They appear as distinct nodes on the canvas and can receive lineage edges.
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
consumers:
|
|
207
|
+
- id: revenue_dashboard # unique ID — used in lineage and layout
|
|
208
|
+
name: "Revenue Dashboard" # display name
|
|
209
|
+
description: "Monthly KPI dashboard for the finance team." # optional
|
|
210
|
+
appearance:
|
|
211
|
+
icon: "📊" # optional (defaults to 📊)
|
|
212
|
+
color: "#e0f2fe" # optional accent color
|
|
213
|
+
url: "https://bi.example.com/revenue" # optional link
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Connect a consumer with lineage by using its `id` as the `to` field:
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
lineage:
|
|
220
|
+
- from: mart_monthly_revenue
|
|
221
|
+
to: revenue_dashboard # consumer ID
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Consumers can also be added to domain `members` lists just like tables.
|
|
225
|
+
|
|
201
226
|
### Annotations
|
|
202
227
|
|
|
203
228
|
```yaml
|
|
@@ -225,7 +250,6 @@ layout:
|
|
|
225
250
|
y: 0
|
|
226
251
|
width: 880
|
|
227
252
|
height: 480
|
|
228
|
-
isLocked: false # prevent drag in canvas
|
|
229
253
|
|
|
230
254
|
# Table inside a domain – coordinates are relative to domain origin
|
|
231
255
|
orders:
|
|
@@ -266,6 +290,162 @@ modscape build ./models -o docs-site
|
|
|
266
290
|
modscape export ./models -o docs/ARCHITECTURE.md
|
|
267
291
|
```
|
|
268
292
|
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## dbt Integration
|
|
296
|
+
|
|
297
|
+
Modscape can import your existing dbt project directly from its compiled `manifest.json`.
|
|
298
|
+
|
|
299
|
+
### Prerequisites
|
|
300
|
+
|
|
301
|
+
Run `dbt parse` (or any dbt command that produces `target/manifest.json`) in your dbt project before using these commands.
|
|
302
|
+
|
|
303
|
+
### Import dbt Project
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
modscape dbt import [project-dir] [options]
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
| Option | Description |
|
|
310
|
+
|--------|-------------|
|
|
311
|
+
| `-o, --output <dir>` | Output directory (default: `modscape-<project-name>`) |
|
|
312
|
+
| `--split-by <key>` | Split output files by `schema`, `tag`, or `folder` |
|
|
313
|
+
|
|
314
|
+
**Examples:**
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# Import from current directory, output to modscape-my_project/
|
|
318
|
+
modscape dbt import
|
|
319
|
+
|
|
320
|
+
# Import from a specific dbt project path
|
|
321
|
+
modscape dbt import ./my_dbt_project
|
|
322
|
+
|
|
323
|
+
# Split into separate YAML files by schema
|
|
324
|
+
modscape dbt import --split-by schema
|
|
325
|
+
|
|
326
|
+
# Split by dbt tag, with custom output directory
|
|
327
|
+
modscape dbt import --split-by tag -o ./modscape-models
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
After import, visualize with:
|
|
331
|
+
```bash
|
|
332
|
+
modscape dev modscape-my_project
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
> **What gets imported:** All `model`, `seed`, `snapshot`, and `source` nodes from `manifest.json`, including columns, descriptions, and lineage (`depends_on`).
|
|
336
|
+
> **Split mode:** When `--split-by` is used, each group is written to a separate YAML file. A self-containment score is shown — files below 80% have cross-file lineage edges that won't render in isolation.
|
|
337
|
+
|
|
338
|
+
### Sync dbt Changes
|
|
339
|
+
|
|
340
|
+
After modifying your dbt project, sync the changes into existing Modscape YAML files without losing any manual edits (layout, appearance, annotations, relationships):
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
modscape dbt sync [project-dir] [options]
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
| Option | Description |
|
|
347
|
+
|--------|-------------|
|
|
348
|
+
| `-o, --output <dir>` | Target directory containing existing Modscape YAML files (default: `modscape-<project-name>`) |
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
# Sync changes from current dbt project
|
|
352
|
+
modscape dbt sync
|
|
353
|
+
|
|
354
|
+
# Sync from a specific path
|
|
355
|
+
modscape dbt sync ./my_dbt_project -o ./modscape-models
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
> **sync vs import:** `import` creates YAML files from scratch; `sync` updates existing files, preserving your manual enrichments (table types, business definitions, sample data, etc.).
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Model File Operations
|
|
363
|
+
|
|
364
|
+
### Merge YAML Files
|
|
365
|
+
|
|
366
|
+
Combine multiple YAML models into one. Duplicate table/domain IDs are resolved with first-wins semantics.
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
modscape merge model-a.yaml model-b.yaml -o merged.yaml
|
|
370
|
+
|
|
371
|
+
# Merge all YAMLs in a directory
|
|
372
|
+
modscape merge ./models -o merged.yaml
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Extract Tables
|
|
376
|
+
|
|
377
|
+
Extract a subset of tables (and their relationships/lineage) into a new YAML file.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
modscape extract model.yaml --tables orders,dim_customers -o subset.yaml
|
|
381
|
+
|
|
382
|
+
# Extract from multiple files
|
|
383
|
+
modscape extract ./models --tables fct_sales,dim_dates -o extracted.yaml
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Auto-Layout
|
|
387
|
+
|
|
388
|
+
Automatically calculate and write layout coordinates based on table relationships.
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
modscape layout model.yaml
|
|
392
|
+
|
|
393
|
+
# Write to a separate output file
|
|
394
|
+
modscape layout model.yaml -o model-with-layout.yaml
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Atomic Model Mutation Commands
|
|
400
|
+
|
|
401
|
+
These commands let AI agents (or scripts) make precise, targeted changes to a YAML model file. All commands support `--json` for machine-readable output.
|
|
402
|
+
|
|
403
|
+
### Table Commands
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
modscape table list <file> # List all table IDs
|
|
407
|
+
modscape table get <file> --id <id> # Get a single table as JSON
|
|
408
|
+
modscape table add <file> --data <json> # Add a new table
|
|
409
|
+
modscape table update <file> --id <id> --data <json> # Update a table
|
|
410
|
+
modscape table remove <file> --id <id> # Remove a table
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Column Commands
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
modscape column add <file> --table <id> --data <json>
|
|
417
|
+
modscape column update <file> --table <id> --id <col-id> --data <json>
|
|
418
|
+
modscape column remove <file> --table <id> --id <col-id>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Relationship Commands
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
modscape relationship list <file>
|
|
425
|
+
modscape relationship add <file> --data <json>
|
|
426
|
+
modscape relationship remove <file> --index <n>
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Lineage Commands
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
modscape lineage list <file>
|
|
433
|
+
modscape lineage add <file> --from <table-id> --to <table-id>
|
|
434
|
+
modscape lineage remove <file> --from <table-id> --to <table-id>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Domain Commands
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
modscape domain list <file>
|
|
441
|
+
modscape domain get <file> --id <id>
|
|
442
|
+
modscape domain add <file> --data <json>
|
|
443
|
+
modscape domain update <file> --id <id> --data <json>
|
|
444
|
+
modscape domain remove <file> --id <id>
|
|
445
|
+
modscape domain member add <file> --domain <id> --table <table-id>
|
|
446
|
+
modscape domain member remove <file> --domain <id> --table <table-id>
|
|
447
|
+
```
|
|
448
|
+
|
|
269
449
|
## Credits
|
|
270
450
|
|
|
271
451
|
Modscape is made possible by these incredible open-source projects:
|
package/package.json
CHANGED
package/src/layout.js
CHANGED
|
@@ -28,7 +28,15 @@ export function applyLayout(inputPath, options = {}) {
|
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
const consumers = Array.isArray(schema.consumers) ? schema.consumers : [];
|
|
32
|
+
const totalNodes = schema.tables.length + consumers.length;
|
|
33
|
+
console.log(` 🏗️ Calculating layout for ${schema.tables.length} tables and ${consumers.length} consumers...`);
|
|
34
|
+
|
|
35
|
+
// Normalize domain members: support both `members` (new) and `tables` (legacy)
|
|
36
|
+
const domains = (schema.domains || []).map(d => ({
|
|
37
|
+
...d,
|
|
38
|
+
members: Array.isArray(d.members) ? d.members : (Array.isArray(d.tables) ? d.tables : []),
|
|
39
|
+
}));
|
|
32
40
|
|
|
33
41
|
// Initialize Dagre Graph
|
|
34
42
|
const g = new dagre.graphlib.Graph({ compound: true });
|
|
@@ -43,11 +51,15 @@ export function applyLayout(inputPath, options = {}) {
|
|
|
43
51
|
|
|
44
52
|
// 1. Add Tables
|
|
45
53
|
schema.tables.forEach((table) => {
|
|
46
|
-
// Standard table size in canvas units
|
|
47
54
|
g.setNode(table.id, { width: 280, height: 160 });
|
|
48
55
|
});
|
|
49
56
|
|
|
50
|
-
// 2. Add
|
|
57
|
+
// 2. Add Consumer nodes
|
|
58
|
+
consumers.forEach((uc) => {
|
|
59
|
+
g.setNode(uc.id, { width: 160, height: 60 });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 3. Add Lineage Edges (tables and consumers)
|
|
51
63
|
if (schema.lineage) {
|
|
52
64
|
schema.lineage.forEach((edge) => {
|
|
53
65
|
if (g.hasNode(edge.from) && g.hasNode(edge.to)) {
|
|
@@ -56,7 +68,7 @@ export function applyLayout(inputPath, options = {}) {
|
|
|
56
68
|
});
|
|
57
69
|
}
|
|
58
70
|
|
|
59
|
-
//
|
|
71
|
+
// 4. Add ER Relationships
|
|
60
72
|
if (schema.relationships) {
|
|
61
73
|
schema.relationships.forEach((rel) => {
|
|
62
74
|
if (g.hasNode(rel.from.table) && g.hasNode(rel.to.table)) {
|
|
@@ -65,34 +77,32 @@ export function applyLayout(inputPath, options = {}) {
|
|
|
65
77
|
});
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
schema.domains.forEach((domain) => {
|
|
80
|
+
// 5. Setup Domains (members can be tables or consumers)
|
|
81
|
+
if (domains.length > 0) {
|
|
82
|
+
domains.forEach((domain) => {
|
|
72
83
|
g.setNode(domain.id, { label: domain.name, cluster: true });
|
|
73
|
-
domain.
|
|
74
|
-
if (g.hasNode(
|
|
75
|
-
g.setParent(
|
|
84
|
+
domain.members.forEach((memberId) => {
|
|
85
|
+
if (g.hasNode(memberId)) {
|
|
86
|
+
g.setParent(memberId, domain.id);
|
|
76
87
|
}
|
|
77
88
|
});
|
|
78
|
-
domainTableMap.set(domain.id, domain.tables);
|
|
79
89
|
});
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
// Execute Layout Calculation
|
|
83
93
|
dagre.layout(g);
|
|
84
94
|
|
|
85
|
-
//
|
|
95
|
+
// 6. Post-process: Convert Dagre results to Modscape Layout format
|
|
86
96
|
const newLayout = {};
|
|
87
97
|
const PAD = 48;
|
|
88
98
|
const TABLE_W = 280;
|
|
89
|
-
const TABLE_H = 160;
|
|
99
|
+
const TABLE_H = 160;
|
|
90
100
|
const GAP = 40;
|
|
91
101
|
|
|
92
102
|
// Process Domains (Grid packing)
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
const members = domain.
|
|
103
|
+
if (domains.length > 0) {
|
|
104
|
+
domains.forEach(domain => {
|
|
105
|
+
const members = domain.members.filter(mid => g.hasNode(mid));
|
|
96
106
|
if (members.length === 0) return;
|
|
97
107
|
|
|
98
108
|
// Sort by dagre rank (left -> right)
|
|
@@ -104,18 +114,17 @@ export function applyLayout(inputPath, options = {}) {
|
|
|
104
114
|
const gridW = cols * (TABLE_W + GAP) - GAP;
|
|
105
115
|
const gridH = rowCount * (TABLE_H + GAP) - GAP;
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const cy = members.reduce((s, tid) => s + g.node(tid).y, 0) / members.length;
|
|
117
|
+
const cx = members.reduce((s, mid) => s + g.node(mid).x, 0) / members.length;
|
|
118
|
+
const cy = members.reduce((s, mid) => s + g.node(mid).y, 0) / members.length;
|
|
110
119
|
const originX = cx - gridW / 2;
|
|
111
120
|
const originY = cy - gridH / 2;
|
|
112
121
|
|
|
113
|
-
members.forEach((
|
|
122
|
+
members.forEach((mid, idx) => {
|
|
114
123
|
const col = idx % cols;
|
|
115
124
|
const row = Math.floor(idx / cols);
|
|
116
125
|
const xOff = col * (TABLE_W + GAP) + TABLE_W / 2;
|
|
117
126
|
const yOff = row * (TABLE_H + GAP) + TABLE_H / 2;
|
|
118
|
-
newLayout[
|
|
127
|
+
newLayout[mid] = {
|
|
119
128
|
x: Math.round(originX + xOff),
|
|
120
129
|
y: Math.round(originY + yOff),
|
|
121
130
|
parentId: domain.id
|
|
@@ -134,18 +143,30 @@ export function applyLayout(inputPath, options = {}) {
|
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
// Standalone Tables
|
|
137
|
-
const
|
|
146
|
+
const domainMemberIds = new Set(domains.flatMap(d => d.members));
|
|
138
147
|
schema.tables.forEach(t => {
|
|
139
|
-
if (!
|
|
148
|
+
if (!domainMemberIds.has(t.id)) {
|
|
140
149
|
const pos = g.node(t.id);
|
|
141
150
|
newLayout[t.id] = { x: Math.round(pos.x), y: Math.round(pos.y) };
|
|
142
151
|
}
|
|
143
152
|
});
|
|
144
153
|
|
|
145
|
-
//
|
|
154
|
+
// Standalone Usecases
|
|
155
|
+
consumers.forEach(uc => {
|
|
156
|
+
if (!domainMemberIds.has(uc.id)) {
|
|
157
|
+
const pos = g.node(uc.id);
|
|
158
|
+
if (pos) newLayout[uc.id] = { x: Math.round(pos.x), y: Math.round(pos.y) };
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// 7. Save back to YAML (write members, not tables, for domains)
|
|
163
|
+
schema.domains = domains.map(d => {
|
|
164
|
+
const { members, ...rest } = d;
|
|
165
|
+
return { ...rest, members };
|
|
166
|
+
});
|
|
146
167
|
schema.layout = newLayout;
|
|
147
168
|
const outputPath = options.output ? path.resolve(process.cwd(), options.output) : absolutePath;
|
|
148
|
-
|
|
169
|
+
|
|
149
170
|
fs.writeFileSync(outputPath, yaml.dump(schema, { indent: 2, lineWidth: -1, noRefs: true }), 'utf8');
|
|
150
171
|
console.log(` ✅ Layout complete! Saved to: ${path.relative(process.cwd(), outputPath)}`);
|
|
151
172
|
}
|