modscape 1.2.0 → 2.0.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 +152 -23
- package/README.md +139 -32
- package/package.json +3 -2
- package/src/export.js +65 -11
- package/src/import-dbt.js +183 -0
- package/src/index.js +38 -1
- package/src/init.js +17 -7
- package/src/merge.js +74 -0
- package/src/sync-dbt.js +149 -0
- package/src/templates/claude/codegen.md +15 -0
- package/src/templates/claude/{command.md → modeling.md} +0 -2
- package/src/templates/codegen-rules.md +141 -0
- package/src/templates/codex/modscape-codegen/SKILL.md +20 -0
- package/src/templates/codex/{SKILL.md → modscape-modeling/SKILL.md} +10 -0
- package/src/templates/gemini/modscape-codegen/SKILL.md +23 -0
- package/src/templates/gemini/{SKILL.md → modscape-modeling/SKILL.md} +5 -3
- package/src/templates/rules.md +680 -72
- package/visualizer/package.json +9 -4
- package/visualizer-dist/assets/index-ChTOGXCm.js +432 -0
- package/visualizer-dist/assets/index-zFK5TNtm.css +1 -0
- package/visualizer-dist/index.html +2 -2
- package/visualizer-dist/assets/index-CTX8C1g5.js +0 -63
- package/visualizer-dist/assets/index-O8OLF7Nv.css +0 -1
package/README.ja.md
CHANGED
|
@@ -40,7 +40,7 @@ https://yujikawa.github.io/modscape/
|
|
|
40
40
|
- オートセーブにより、ローカルのYAMLを常に最新の状態に維持。
|
|
41
41
|
- **ダーク/ライトモード対応**: 利用環境やドキュメント作成の用途に合わせて、ワンクリックでテーマを切り替え可能。
|
|
42
42
|
- **データ分析特化のモデリング**: `fact`, `dimension`, `mart`, `hub`, `link`, `satellite` に加え、汎用的な `table` タイプを標準サポート。
|
|
43
|
-
- **AIエージェント対応**: **Gemini, Claude, Codex**
|
|
43
|
+
- **AIエージェント対応**: **Gemini CLI, Claude Code, Codex** 用の雛形を内蔵。モデリング(`/modscape:modeling`)と実装コード生成(`/modscape:codegen`)の両方でLLMを活用できます。
|
|
44
44
|
|
|
45
45
|
## インストール
|
|
46
46
|
|
|
@@ -53,15 +53,27 @@ npm install -g modscape
|
|
|
53
53
|
## はじめに
|
|
54
54
|
|
|
55
55
|
### A: AI駆動のモデリング(推奨)
|
|
56
|
-
1. **初期化**: 使用するAI
|
|
56
|
+
1. **初期化**: 使用するAIエージェントに合わせてルールファイルとコマンドを生成します。
|
|
57
57
|
```bash
|
|
58
|
-
modscape init --gemini
|
|
58
|
+
modscape init --gemini # Gemini CLI
|
|
59
|
+
modscape init --claude # Claude Code
|
|
60
|
+
modscape init --codex # Codex
|
|
61
|
+
modscape init --all # 3つすべて
|
|
59
62
|
```
|
|
63
|
+
`.modscape/rules.md`(YAMLスキーマのルール)と `.modscape/codegen-rules.md`(実装コード生成のルール)、および各エージェント用のコマンドファイルが生成されます。
|
|
64
|
+
|
|
60
65
|
2. **起動**: ビジュアライザーを起動します。
|
|
61
66
|
```bash
|
|
62
67
|
modscape dev model.yaml
|
|
63
68
|
```
|
|
64
|
-
|
|
69
|
+
|
|
70
|
+
3. **データモデルの設計** — `/modscape:modeling` でモデルを作成・編集します。
|
|
71
|
+
> *".modscape/rules.md のルールに従って、model.yaml に新しい 'Marketing' ドメインを追加して。"*
|
|
72
|
+
|
|
73
|
+
4. **実装コードの生成** — `/modscape:codegen` でYAMLをdbt / SQLMesh / Spark SQLに変換します。
|
|
74
|
+
> *".modscape/codegen-rules.md に従って、model.yaml からdbtモデルを生成して。"*
|
|
75
|
+
|
|
76
|
+
エージェントは `lineage.upstream` を元に依存関係の順でモデルを生成し、YAMLで定義しきれない箇所には `-- TODO:` コメントを残します。
|
|
65
77
|
|
|
66
78
|
### B: 手動モデリング
|
|
67
79
|
1. **YAML作成**: `model.yaml` ファイルを作成します。
|
|
@@ -74,39 +86,157 @@ npm install -g modscape
|
|
|
74
86
|
|
|
75
87
|
## モデルの定義 (YAML)
|
|
76
88
|
|
|
89
|
+
YAMLのルートレベル構造は以下の通りです:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
domains – 関連テーブルをまとめるビジュアルコンテナ
|
|
93
|
+
tables – 3階層メタデータを持つエンティティ定義
|
|
94
|
+
relationships – テーブル間のERカーディナリティ
|
|
95
|
+
annotations – キャンバス上のスティッキーノート・吹き出し
|
|
96
|
+
layout – 全座標データ(tables/domains の中に x/y を書いてはいけない)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Domains(ドメイン)
|
|
100
|
+
|
|
77
101
|
```yaml
|
|
78
|
-
# 1. Domains: 関連するテーブルをグループ化するコンテナ
|
|
79
102
|
domains:
|
|
80
103
|
- id: core_sales
|
|
81
|
-
name: 主要売上
|
|
82
|
-
|
|
83
|
-
|
|
104
|
+
name: "主要売上"
|
|
105
|
+
description: "営業チームのトランザクションデータ。" # 任意
|
|
106
|
+
color: "rgba(59, 130, 246, 0.1)" # 背景色
|
|
107
|
+
tables: [orders, dim_customers]
|
|
108
|
+
isLocked: false # true にするとキャンバスでの誤ドラッグを防止
|
|
109
|
+
```
|
|
84
110
|
|
|
85
|
-
|
|
111
|
+
### Tables(テーブル)
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
86
114
|
tables:
|
|
87
115
|
- id: orders
|
|
88
|
-
name: 注文
|
|
89
|
-
logical_name: "顧客注文履歴"
|
|
90
|
-
physical_name: "fct_retail_sales"
|
|
116
|
+
name: 注文 # 概念名(大)
|
|
117
|
+
logical_name: "顧客注文履歴" # 論理名(中)
|
|
118
|
+
physical_name: "fct_retail_sales" # 物理名(小)
|
|
119
|
+
|
|
91
120
|
appearance:
|
|
92
|
-
type: fact
|
|
93
|
-
sub_type: transaction
|
|
94
|
-
|
|
121
|
+
type: fact # fact | dimension | mart | hub | link | satellite | table
|
|
122
|
+
sub_type: transaction # transaction | periodic | accumulating など
|
|
123
|
+
scd: type2 # ディメンション用 SCD タイプ: type0〜type6
|
|
124
|
+
icon: "💰"
|
|
125
|
+
color: "#e0f2fe" # 任意のヘッダーカラー
|
|
126
|
+
|
|
127
|
+
conceptual: # 任意 – AIエージェント向けビジネスコンテキスト
|
|
128
|
+
description: "1行 = 1注文明細。"
|
|
129
|
+
tags: [WHO, WHAT, WHEN] # BEAM* タグ
|
|
130
|
+
businessDefinitions:
|
|
131
|
+
revenue: "割引・返品後の純売上"
|
|
132
|
+
|
|
133
|
+
lineage: # mart/集計テーブルのみに定義
|
|
134
|
+
upstream:
|
|
135
|
+
- fct_sales
|
|
136
|
+
- dim_dates
|
|
137
|
+
|
|
138
|
+
implementation: # 任意 – AIコード生成へのヒント
|
|
139
|
+
materialization: incremental # table | view | incremental | ephemeral
|
|
140
|
+
incremental_strategy: merge # merge | append | delete+insert
|
|
141
|
+
unique_key: order_id
|
|
142
|
+
partition_by:
|
|
143
|
+
field: order_date # DATE/TIMESTAMP型カラムを指定(サロゲートキーは不可)
|
|
144
|
+
granularity: day # day | month | year | hour
|
|
145
|
+
cluster_by: [customer_id]
|
|
146
|
+
grain: [month_key] # GROUP BY カラム(martのみ)
|
|
147
|
+
measures: # 集計定義(martのみ)
|
|
148
|
+
- column: total_revenue
|
|
149
|
+
agg: sum # sum | count | count_distinct | avg | min | max
|
|
150
|
+
source_column: fct_sales.amount
|
|
151
|
+
|
|
95
152
|
columns:
|
|
96
153
|
- id: order_id
|
|
97
154
|
logical:
|
|
98
|
-
name:
|
|
155
|
+
name: "注文ID"
|
|
156
|
+
type: Int # Int | String | Decimal | Date | Timestamp | Boolean など
|
|
157
|
+
description: "サロゲートキー。"
|
|
99
158
|
isPrimaryKey: true
|
|
100
|
-
|
|
101
|
-
|
|
159
|
+
isForeignKey: false
|
|
160
|
+
isPartitionKey: false
|
|
161
|
+
isMetadata: false # 監査カラム(load_date, record_source)は true
|
|
162
|
+
additivity: fully # fully | semi | non
|
|
163
|
+
physical: # 任意 – ウェアハウスの物理定義を上書き
|
|
164
|
+
name: order_id
|
|
165
|
+
type: "BIGINT"
|
|
166
|
+
constraints: [NOT NULL]
|
|
167
|
+
|
|
168
|
+
sampleData: # 2次元配列。先頭行 = カラムID
|
|
102
169
|
- [order_id, amount, status]
|
|
103
170
|
- [1001, 50.0, "COMPLETED"]
|
|
171
|
+
- [1002, 120.5, "PENDING"]
|
|
172
|
+
```
|
|
104
173
|
|
|
105
|
-
|
|
174
|
+
**テーブルタイプと `appearance.type` の使い分け:**
|
|
175
|
+
|
|
176
|
+
| type | 用途 |
|
|
177
|
+
|------|------|
|
|
178
|
+
| `fact` | 取引・イベント・測定値 |
|
|
179
|
+
| `dimension` | エンティティ・マスタ・参照リスト |
|
|
180
|
+
| `mart` | 集計・消費者向けテーブル(`lineage.upstream` を必ず定義) |
|
|
181
|
+
| `hub` | Data Vault のビジネスキー |
|
|
182
|
+
| `link` | Data Vault のハブ間結合・トランザクション |
|
|
183
|
+
| `satellite` | Data Vault のハブに紐づく履歴属性 |
|
|
184
|
+
| `table` | 汎用 |
|
|
185
|
+
|
|
186
|
+
### Relationships(リレーションシップ)
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
106
189
|
relationships:
|
|
107
|
-
- from:
|
|
108
|
-
|
|
109
|
-
|
|
190
|
+
- from:
|
|
191
|
+
table: dim_customers # テーブル ID
|
|
192
|
+
column: customer_id # カラム ID(任意)
|
|
193
|
+
to:
|
|
194
|
+
table: fct_orders
|
|
195
|
+
column: customer_id
|
|
196
|
+
type: one-to-many # one-to-one | one-to-many | many-to-one | many-to-many
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
> **データリネージ**は `lineage.upstream` で定義し、リネージモードでアニメーション矢印として表示されます。`relationships` に重複して記載しないでください。
|
|
200
|
+
|
|
201
|
+
### Annotations(アノテーション)
|
|
202
|
+
|
|
203
|
+
```yaml
|
|
204
|
+
annotations:
|
|
205
|
+
- id: note_001
|
|
206
|
+
type: sticky # sticky | callout
|
|
207
|
+
text: "粒度:1行 = 1注文明細"
|
|
208
|
+
color: "#fef9c3" # 任意の背景色
|
|
209
|
+
targetId: fct_orders # 貼り付け先のオブジェクト ID(任意)
|
|
210
|
+
targetType: table # table | domain | relationship | column
|
|
211
|
+
offset:
|
|
212
|
+
x: 100 # 対象の左上からのオフセット(targetId 未指定時は絶対座標)
|
|
213
|
+
y: -80
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Layout(レイアウト)
|
|
217
|
+
|
|
218
|
+
全座標データはオブジェクト ID をキーとして `layout` に記述します。**`tables` や `domains` の中に `x`/`y` を書いてはいけません。**
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
layout:
|
|
222
|
+
# ドメイン – width と height が必要
|
|
223
|
+
core_sales:
|
|
224
|
+
x: 0
|
|
225
|
+
y: 0
|
|
226
|
+
width: 880
|
|
227
|
+
height: 480
|
|
228
|
+
isLocked: false # true でキャンバスのドラッグを防止
|
|
229
|
+
|
|
230
|
+
# ドメイン内のテーブル – 座標はドメインの原点からの相対値
|
|
231
|
+
orders:
|
|
232
|
+
x: 280
|
|
233
|
+
y: 200
|
|
234
|
+
parentId: core_sales # ドメインへの所属を宣言
|
|
235
|
+
|
|
236
|
+
# スタンドアロンテーブル – キャンバス絶対座標
|
|
237
|
+
mart_summary:
|
|
238
|
+
x: 1060
|
|
239
|
+
y: 200
|
|
110
240
|
```
|
|
111
241
|
|
|
112
242
|
---
|
|
@@ -140,7 +270,6 @@ modscape export ./models -o docs/ARCHITECTURE.md
|
|
|
140
270
|
|
|
141
271
|
Modscape は以下の素晴らしいオープンソースプロジェクトによって支えられています:
|
|
142
272
|
|
|
143
|
-
- [React Flow](https://reactflow.dev/) - インタラクティブなグラフ UI フレームワーク。
|
|
144
273
|
- [CodeMirror 6](https://codemirror.net/) - 次世代のウェブベース・コードエディタ。
|
|
145
274
|
- [Dagre](https://github.com/dagrejs/dagre) - 階層型グラフ・レイアウトエンジン。
|
|
146
275
|
- [Lucide React](https://lucide.dev/) - シンプルで美しいアイコンセット。
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ In modern data analysis platforms, data modeling is no longer just about drawing
|
|
|
37
37
|
- Optional **Auto-save** ensures your local YAML is always up-to-date.
|
|
38
38
|
- **Dark/Light Mode Support**: Switch between themes seamlessly for better eye comfort or documentation exports.
|
|
39
39
|
- **Specialized Modeling Types**: Native support for entity types like `fact`, `dimension`, `mart`, `hub`, `link`, `satellite`, and generic `table`.
|
|
40
|
-
- **AI-Agent Ready**: Built-in scaffolding for **Gemini, Claude, and Codex**
|
|
40
|
+
- **AI-Agent Ready**: Built-in scaffolding for **Gemini CLI, Claude Code, and Codex** — both for modeling (`/modscape:modeling`) and implementation code generation (`/modscape:codegen`).
|
|
41
41
|
|
|
42
42
|
## Installation
|
|
43
43
|
|
|
@@ -54,22 +54,27 @@ npm install -g modscape
|
|
|
54
54
|
### Path A: AI-Driven Modeling (Recommended)
|
|
55
55
|
Leverage AI coding assistants (**Gemini CLI, Claude Code, or Codex**) to build your models.
|
|
56
56
|
|
|
57
|
-
1. **Initialize**: Scaffold modeling rules and
|
|
57
|
+
1. **Initialize**: Scaffold modeling rules and commands for your preferred agent.
|
|
58
58
|
```bash
|
|
59
|
-
#
|
|
60
|
-
modscape init --
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
modscape init --claude
|
|
64
|
-
|
|
65
|
-
# For Codex
|
|
66
|
-
modscape init --codex
|
|
59
|
+
modscape init --gemini # Gemini CLI
|
|
60
|
+
modscape init --claude # Claude Code
|
|
61
|
+
modscape init --codex # Codex
|
|
62
|
+
modscape init --all # all three
|
|
67
63
|
```
|
|
64
|
+
This creates `.modscape/rules.md` (YAML schema rules) and `.modscape/codegen-rules.md` (code generation rules), plus agent-specific command files.
|
|
65
|
+
|
|
68
66
|
2. **Start Dev**: Launch the visualizer.
|
|
69
67
|
```bash
|
|
70
68
|
modscape dev model.yaml
|
|
71
69
|
```
|
|
72
|
-
|
|
70
|
+
|
|
71
|
+
3. **Model with AI** — use `/modscape:modeling` to design your data model:
|
|
72
|
+
> *"Use the rules in .modscape/rules.md to add a new 'Marketing' domain with a 'campaign_performance' fact table."*
|
|
73
|
+
|
|
74
|
+
4. **Generate implementation code** — use `/modscape:codegen` to turn your YAML into dbt / SQLMesh / Spark SQL:
|
|
75
|
+
> *"Follow .modscape/codegen-rules.md and generate dbt models from model.yaml."*
|
|
76
|
+
|
|
77
|
+
The agent generates models in the correct dependency order and adds `-- TODO:` comments wherever the YAML doesn't fully specify the logic.
|
|
73
78
|
|
|
74
79
|
### Path B: Manual Modeling
|
|
75
80
|
Best for direct architectural control.
|
|
@@ -84,42 +89,145 @@ Best for direct architectural control.
|
|
|
84
89
|
|
|
85
90
|
## Defining Your Model (YAML)
|
|
86
91
|
|
|
87
|
-
Modscape uses a schema designed for data analysis contexts.
|
|
92
|
+
Modscape uses a schema designed for data analysis contexts. The full YAML structure is:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
domains – visual containers grouping related tables
|
|
96
|
+
tables – entity definitions with tri-layer metadata
|
|
97
|
+
relationships – ER cardinality between tables
|
|
98
|
+
annotations – sticky notes / callouts on the canvas
|
|
99
|
+
layout – ALL coordinate data (never put x/y inside tables or domains)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Domains
|
|
88
103
|
|
|
89
104
|
```yaml
|
|
90
|
-
# 1. Domains: Visual containers for grouping business logic
|
|
91
105
|
domains:
|
|
92
106
|
- id: core_sales
|
|
93
|
-
name: Core Sales
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
name: "Core Sales"
|
|
108
|
+
description: "Transactional data for the sales team." # optional
|
|
109
|
+
color: "rgba(59, 130, 246, 0.1)" # background fill
|
|
110
|
+
tables: [orders, dim_customers]
|
|
111
|
+
isLocked: false # prevent accidental drag when true
|
|
112
|
+
```
|
|
96
113
|
|
|
97
|
-
|
|
114
|
+
### Tables
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
98
117
|
tables:
|
|
99
118
|
- id: orders
|
|
100
|
-
name: Orders
|
|
101
|
-
logical_name: "Customer Purchase Record"
|
|
102
|
-
physical_name: "fct_retail_sales"
|
|
119
|
+
name: Orders # Conceptual name (large)
|
|
120
|
+
logical_name: "Customer Purchase Record" # Logical name (medium)
|
|
121
|
+
physical_name: "fct_retail_sales" # Physical table name (small)
|
|
122
|
+
|
|
103
123
|
appearance:
|
|
104
|
-
type: fact
|
|
105
|
-
sub_type: transaction
|
|
106
|
-
|
|
124
|
+
type: fact # fact | dimension | mart | hub | link | satellite | table
|
|
125
|
+
sub_type: transaction # transaction | periodic | accumulating | etc.
|
|
126
|
+
scd: type2 # SCD type for dimensions: type0–type6
|
|
127
|
+
icon: "💰"
|
|
128
|
+
color: "#e0f2fe" # optional custom header color
|
|
129
|
+
|
|
130
|
+
conceptual: # optional – business context for AI agents
|
|
131
|
+
description: "One row per order line item."
|
|
132
|
+
tags: [WHO, WHAT, WHEN] # BEAM* tags
|
|
133
|
+
businessDefinitions:
|
|
134
|
+
revenue: "Net revenue after discounts"
|
|
135
|
+
|
|
136
|
+
lineage: # for mart/aggregated tables only
|
|
137
|
+
upstream:
|
|
138
|
+
- fct_sales
|
|
139
|
+
- dim_dates
|
|
140
|
+
|
|
141
|
+
implementation: # optional – hints for AI code generation
|
|
142
|
+
materialization: incremental # table | view | incremental | ephemeral
|
|
143
|
+
incremental_strategy: merge # merge | append | delete+insert
|
|
144
|
+
unique_key: order_id
|
|
145
|
+
partition_by:
|
|
146
|
+
field: order_date # use a DATE/TIMESTAMP column, not a surrogate key
|
|
147
|
+
granularity: day # day | month | year | hour
|
|
148
|
+
cluster_by: [customer_id]
|
|
149
|
+
grain: [month_key] # GROUP BY columns (mart only)
|
|
150
|
+
measures: # aggregation definitions (mart only)
|
|
151
|
+
- column: total_revenue
|
|
152
|
+
agg: sum # sum | count | count_distinct | avg | min | max
|
|
153
|
+
source_column: fct_sales.amount
|
|
154
|
+
|
|
107
155
|
columns:
|
|
108
156
|
- id: order_id
|
|
109
157
|
logical:
|
|
110
|
-
name:
|
|
111
|
-
type: Int
|
|
158
|
+
name: "Order ID"
|
|
159
|
+
type: Int # Int | String | Decimal | Date | Timestamp | Boolean | ...
|
|
160
|
+
description: "Surrogate key."
|
|
112
161
|
isPrimaryKey: true
|
|
113
|
-
|
|
114
|
-
|
|
162
|
+
isForeignKey: false
|
|
163
|
+
isPartitionKey: false
|
|
164
|
+
isMetadata: false # true for audit cols (load_date, record_source)
|
|
165
|
+
additivity: fully # fully | semi | non
|
|
166
|
+
physical: # optional warehouse overrides
|
|
167
|
+
name: order_id
|
|
168
|
+
type: "BIGINT"
|
|
169
|
+
constraints: [NOT NULL]
|
|
170
|
+
|
|
171
|
+
sampleData: # 2D array; first row = column IDs
|
|
115
172
|
- [order_id, amount, status]
|
|
116
173
|
- [1001, 50.0, "COMPLETED"]
|
|
174
|
+
- [1002, 120.5, "PENDING"]
|
|
175
|
+
```
|
|
117
176
|
|
|
118
|
-
|
|
177
|
+
### Relationships
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
119
180
|
relationships:
|
|
120
|
-
- from:
|
|
121
|
-
|
|
122
|
-
|
|
181
|
+
- from:
|
|
182
|
+
table: dim_customers # table ID
|
|
183
|
+
column: customer_id # column ID (optional)
|
|
184
|
+
to:
|
|
185
|
+
table: fct_orders
|
|
186
|
+
column: customer_id
|
|
187
|
+
type: one-to-many # one-to-one | one-to-many | many-to-one | many-to-many
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
> **Data Lineage** connections are driven by `lineage.upstream` and rendered as animated arrows in Lineage Mode. Do **not** duplicate them as `relationships` entries.
|
|
191
|
+
|
|
192
|
+
### Annotations
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
annotations:
|
|
196
|
+
- id: note_001
|
|
197
|
+
type: sticky # sticky | callout
|
|
198
|
+
text: "Grain: one row per invoice line item."
|
|
199
|
+
color: "#fef9c3" # optional background color
|
|
200
|
+
targetId: fct_orders # ID of the attached object (optional)
|
|
201
|
+
targetType: table # table | domain | relationship | column
|
|
202
|
+
offset:
|
|
203
|
+
x: 100 # offset from target's top-left (or absolute if no target)
|
|
204
|
+
y: -80
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Layout
|
|
208
|
+
|
|
209
|
+
All coordinate data lives in `layout`, keyed by object ID. **Never** place `x`/`y` inside `tables` or `domains`.
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
layout:
|
|
213
|
+
# Domain – requires width and height
|
|
214
|
+
core_sales:
|
|
215
|
+
x: 0
|
|
216
|
+
y: 0
|
|
217
|
+
width: 880
|
|
218
|
+
height: 480
|
|
219
|
+
isLocked: false # prevent drag in canvas
|
|
220
|
+
|
|
221
|
+
# Table inside a domain – coordinates are relative to domain origin
|
|
222
|
+
orders:
|
|
223
|
+
x: 280
|
|
224
|
+
y: 200
|
|
225
|
+
parentId: core_sales # declare domain membership
|
|
226
|
+
|
|
227
|
+
# Standalone table – absolute canvas coordinates
|
|
228
|
+
mart_summary:
|
|
229
|
+
x: 1060
|
|
230
|
+
y: 200
|
|
123
231
|
```
|
|
124
232
|
|
|
125
233
|
---
|
|
@@ -153,7 +261,6 @@ modscape export ./models -o docs/ARCHITECTURE.md
|
|
|
153
261
|
|
|
154
262
|
Modscape is made possible by these incredible open-source projects:
|
|
155
263
|
|
|
156
|
-
- [React Flow](https://reactflow.dev/) - Interactive node-based UI framework.
|
|
157
264
|
- [CodeMirror 6](https://codemirror.net/) - Next-generation code editor for the web.
|
|
158
265
|
- [Dagre](https://github.com/dagrejs/dagre) - Directed graph layout engine.
|
|
159
266
|
- [Lucide React](https://lucide.dev/) - Beautifully simple pixel-perfect icons.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modscape",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Modscape: A YAML-driven data modeling visualizer CLI",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build-ui": "cd visualizer && npm run build && rm -rf ../visualizer-dist && mv dist ../visualizer-dist",
|
|
22
22
|
"dev": "node src/index.js dev",
|
|
23
|
+
"test:cli": "playwright test tests/import-dbt.spec.ts",
|
|
23
24
|
"test:e2e": "cp tests/fixtures/test-model.yaml tests/fixtures/test-model-runtime.yaml && playwright test",
|
|
24
|
-
"test:all": "npm run build-ui &&
|
|
25
|
+
"test:all": "npm run build-ui && npm run test:cli",
|
|
25
26
|
"test:update": "npm run build-ui && sleep 1 && npm run test:e2e -- --update-snapshots"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
package/src/export.js
CHANGED
|
@@ -67,16 +67,13 @@ function generateMermaidLineage(schema) {
|
|
|
67
67
|
let mermaid = 'graph TD\n';
|
|
68
68
|
let hasLineage = false;
|
|
69
69
|
|
|
70
|
-
schema.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
mermaid += ` ${sourceName} --> ${targetName}\n`;
|
|
78
|
-
});
|
|
79
|
-
}
|
|
70
|
+
(schema.lineage || []).forEach(edge => {
|
|
71
|
+
hasLineage = true;
|
|
72
|
+
const sourceTable = schema.tables.find(t => t.id === edge.from);
|
|
73
|
+
const targetTable = schema.tables.find(t => t.id === edge.to);
|
|
74
|
+
const sourceName = sanitize(sourceTable?.name || edge.from);
|
|
75
|
+
const targetName = sanitize(targetTable?.name || edge.to);
|
|
76
|
+
mermaid += ` ${sourceName} --> ${targetName}\n`;
|
|
80
77
|
});
|
|
81
78
|
|
|
82
79
|
return hasLineage ? mermaid : null;
|
|
@@ -180,11 +177,68 @@ export function generateMarkdown(schema, modelName) {
|
|
|
180
177
|
md += '## Table Catalog\n\n';
|
|
181
178
|
schema.tables.forEach(table => {
|
|
182
179
|
md += `### ${table.name} ${table.appearance?.icon || ''}\n`;
|
|
180
|
+
if (table.logical_name) md += `*${table.logical_name}*\n\n`;
|
|
183
181
|
if (table.conceptual?.description) {
|
|
184
182
|
md += `**Description**: ${table.conceptual.description}\n\n`;
|
|
185
183
|
}
|
|
184
|
+
|
|
185
|
+
// Type + SCD
|
|
186
186
|
if (table.appearance?.type) {
|
|
187
|
-
|
|
187
|
+
const scd = table.appearance.scd ? ` · SCD ${table.appearance.scd}` : '';
|
|
188
|
+
md += `**Type**: \`${table.appearance.type.toUpperCase()}\`${scd}\n\n`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Physical name
|
|
192
|
+
if (table.physical_name) {
|
|
193
|
+
md += `**Physical Name**: \`${table.physical_name}\`\n\n`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Lineage
|
|
197
|
+
const upstreamEdges = (schema.lineage || []).filter(e => e.to === table.id);
|
|
198
|
+
if (upstreamEdges.length > 0) {
|
|
199
|
+
const upstreamNames = upstreamEdges.map(e => {
|
|
200
|
+
const t = schema.tables.find(t => t.id === e.from);
|
|
201
|
+
return t ? t.name : e.from;
|
|
202
|
+
});
|
|
203
|
+
md += `**Upstream**: ${upstreamNames.map(n => `\`${n}\``).join(' → ')}\n\n`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Implementation
|
|
207
|
+
if (table.implementation) {
|
|
208
|
+
const impl = table.implementation;
|
|
209
|
+
md += '#### Implementation\n\n';
|
|
210
|
+
md += '| Property | Value |\n| --- | --- |\n';
|
|
211
|
+
if (impl.materialization) md += `| Materialization | \`${impl.materialization}\` |\n`;
|
|
212
|
+
if (impl.incremental_strategy) md += `| Incremental Strategy | \`${impl.incremental_strategy}\` |\n`;
|
|
213
|
+
if (impl.unique_key) {
|
|
214
|
+
const uk = Array.isArray(impl.unique_key) ? impl.unique_key.join(', ') : impl.unique_key;
|
|
215
|
+
md += `| Unique Key | \`${uk}\` |\n`;
|
|
216
|
+
}
|
|
217
|
+
if (impl.partition_by) {
|
|
218
|
+
const pb = impl.partition_by;
|
|
219
|
+
const field = pb.field || pb[0]?.field;
|
|
220
|
+
const gran = pb.granularity || pb[0]?.granularity;
|
|
221
|
+
md += `| Partition By | \`${field}\`${gran ? ` (${gran})` : ''} |\n`;
|
|
222
|
+
}
|
|
223
|
+
if (impl.cluster_by) {
|
|
224
|
+
const cb = Array.isArray(impl.cluster_by) ? impl.cluster_by.join(', ') : impl.cluster_by;
|
|
225
|
+
md += `| Cluster By | \`${cb}\` |\n`;
|
|
226
|
+
}
|
|
227
|
+
if (impl.grain) {
|
|
228
|
+
const grain = Array.isArray(impl.grain) ? impl.grain.join(', ') : impl.grain;
|
|
229
|
+
md += `| Grain (GROUP BY) | \`${grain}\` |\n`;
|
|
230
|
+
}
|
|
231
|
+
md += '\n';
|
|
232
|
+
|
|
233
|
+
// Measures
|
|
234
|
+
if (impl.measures?.length > 0) {
|
|
235
|
+
md += '#### Measures\n\n';
|
|
236
|
+
md += '| Column | Aggregation | Source Column |\n| --- | --- | --- |\n';
|
|
237
|
+
impl.measures.forEach(m => {
|
|
238
|
+
md += `| \`${m.column}\` | \`${m.agg}\` | ${m.source_column ? `\`${m.source_column}\`` : '-'} |\n`;
|
|
239
|
+
});
|
|
240
|
+
md += '\n';
|
|
241
|
+
}
|
|
188
242
|
}
|
|
189
243
|
|
|
190
244
|
// Columns Table (Enhanced with Physical info)
|