dbml-metaquery 0.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.md +117 -0
- package/dist/cli.js +17445 -0
- package/dist/cli.js.map +32 -0
- package/dist/graph.d.ts +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17215 -0
- package/dist/index.js.map +31 -0
- package/dist/types.d.ts +63 -0
- package/package.json +44 -0
- package/src/cli.ts +271 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the DBML Path Finder.
|
|
3
|
+
*/
|
|
4
|
+
export interface PathStep {
|
|
5
|
+
from: {
|
|
6
|
+
table: string;
|
|
7
|
+
column: string;
|
|
8
|
+
};
|
|
9
|
+
to: {
|
|
10
|
+
table: string;
|
|
11
|
+
column: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface Relationship {
|
|
15
|
+
childTable: string;
|
|
16
|
+
childColumn: string;
|
|
17
|
+
parentTable: string;
|
|
18
|
+
parentColumn: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TableInfo {
|
|
21
|
+
name: string;
|
|
22
|
+
note?: string;
|
|
23
|
+
columns: ColumnInfo[];
|
|
24
|
+
}
|
|
25
|
+
export interface ColumnInfo {
|
|
26
|
+
name: string;
|
|
27
|
+
type: string;
|
|
28
|
+
note?: string;
|
|
29
|
+
fk?: {
|
|
30
|
+
table: string;
|
|
31
|
+
column: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface GroupInfo {
|
|
35
|
+
name: string;
|
|
36
|
+
tables: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface ReferencingTable {
|
|
39
|
+
table: string;
|
|
40
|
+
column: string;
|
|
41
|
+
myColumn: string;
|
|
42
|
+
}
|
|
43
|
+
export interface Neighbors {
|
|
44
|
+
parents: {
|
|
45
|
+
table: string;
|
|
46
|
+
via: string;
|
|
47
|
+
}[];
|
|
48
|
+
children: {
|
|
49
|
+
table: string;
|
|
50
|
+
via: string;
|
|
51
|
+
}[];
|
|
52
|
+
}
|
|
53
|
+
export interface GroupSummary {
|
|
54
|
+
name: string;
|
|
55
|
+
tableCount: number;
|
|
56
|
+
tables: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface SearchResult {
|
|
59
|
+
table: string;
|
|
60
|
+
match: "table_name" | "table_note" | "column_name" | "column_note";
|
|
61
|
+
column?: string;
|
|
62
|
+
text: string;
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dbml-metaquery",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Parse DBML into a navigable graph with rich metadata -- FK path finding, table info, groups, and schema search.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dbml-metaquery": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "rm -rf dist && bun build src/index.ts src/cli.ts --outdir dist --target node --format esm --sourcemap=linked && printf '#!/usr/bin/env node\\n' | cat - dist/cli.js > dist/cli.tmp && mv dist/cli.tmp dist/cli.js && bunx tsc --project tsconfig.build.json",
|
|
11
|
+
"cli": "bun run src/cli.ts",
|
|
12
|
+
"test": "bun test",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"src/cli.ts"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"bun": "./src/index.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@dbml/parse": "^7.0.0-alpha.0",
|
|
31
|
+
"graphology": "^0.25.4",
|
|
32
|
+
"graphology-shortest-path": "^2.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/bun": "latest",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
},
|
|
38
|
+
"keywords": ["dbml", "database", "schema", "graph", "path-finding", "metadata"],
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/ada-lovecraft/dbml-metaquery.git"
|
|
42
|
+
},
|
|
43
|
+
"license": "MIT"
|
|
44
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI for dbml-metaquery.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* dbml-metaquery <dbml-path> find-path <from> <to>
|
|
6
|
+
* dbml-metaquery <dbml-path> info <table>
|
|
7
|
+
* dbml-metaquery <dbml-path> neighbors <table>
|
|
8
|
+
* dbml-metaquery <dbml-path> refs-to <table>
|
|
9
|
+
* dbml-metaquery <dbml-path> rels <table>
|
|
10
|
+
* dbml-metaquery <dbml-path> search <query>
|
|
11
|
+
* dbml-metaquery <dbml-path> summary
|
|
12
|
+
* dbml-metaquery <dbml-path> tables
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync, existsSync } from "node:fs"
|
|
16
|
+
import { resolve } from "node:path"
|
|
17
|
+
import { DbmlGraph } from "./graph"
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2)
|
|
20
|
+
|
|
21
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
22
|
+
console.log("Usage: dbml-metaquery <dbml-path> <command> [args]")
|
|
23
|
+
console.log()
|
|
24
|
+
console.log("Parse DBML into a navigable graph with rich metadata.")
|
|
25
|
+
console.log()
|
|
26
|
+
console.log("Commands:")
|
|
27
|
+
console.log(" find-path <from> <to> Find shortest FK path between tables")
|
|
28
|
+
console.log(" info <table> Show table metadata (note, columns, group)")
|
|
29
|
+
console.log(" neighbors <table> Show tables directly connected via FK")
|
|
30
|
+
console.log(" refs-to <table> Show tables that reference this table")
|
|
31
|
+
console.log(" rels <table> Show all FK relationships for a table")
|
|
32
|
+
console.log(" search <query> Search table/column names and notes")
|
|
33
|
+
console.log(" summary Show schema overview (groups and table counts)")
|
|
34
|
+
console.log(" tables List all table names")
|
|
35
|
+
process.exit(0)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const dbmlPath = resolve(args[0]!)
|
|
39
|
+
if (!existsSync(dbmlPath)) {
|
|
40
|
+
console.error(`DBML file not found: ${dbmlPath}`)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const command = args[1]
|
|
45
|
+
if (!command) {
|
|
46
|
+
console.error("No command provided. Use --help for usage.")
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dbml = readFileSync(dbmlPath, "utf-8")
|
|
51
|
+
const graph = new DbmlGraph(dbml)
|
|
52
|
+
const cmdArgs = args.slice(2)
|
|
53
|
+
|
|
54
|
+
switch (command) {
|
|
55
|
+
case "tables": {
|
|
56
|
+
const tables = graph.getTables()
|
|
57
|
+
console.log(`${tables.length} tables:\n`)
|
|
58
|
+
for (const t of tables) {
|
|
59
|
+
console.log(` ${t}`)
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case "summary": {
|
|
65
|
+
const summary = graph.getSummary()
|
|
66
|
+
const total = summary.reduce((n, g) => n + g.tableCount, 0)
|
|
67
|
+
console.log(`${total} tables in ${summary.length} groups:\n`)
|
|
68
|
+
for (const group of summary) {
|
|
69
|
+
const color = graph.getGroupColor(group.name)
|
|
70
|
+
const colorStr = color ? ` \x1b[2m${color}\x1b[0m` : ""
|
|
71
|
+
console.log(` \x1b[1m${group.name}\x1b[0m (${group.tableCount})${colorStr}`)
|
|
72
|
+
for (const t of group.tables) {
|
|
73
|
+
console.log(` ${t}`)
|
|
74
|
+
}
|
|
75
|
+
console.log()
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case "info": {
|
|
81
|
+
if (!cmdArgs[0]) {
|
|
82
|
+
console.error("Usage: dbml-metaquery <dbml-path> info <table>")
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}
|
|
85
|
+
const table = graph.getTable(cmdArgs[0])
|
|
86
|
+
if (!table) {
|
|
87
|
+
console.error(`Table "${cmdArgs[0]}" not found`)
|
|
88
|
+
process.exit(1)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`\x1b[1m${table.name}\x1b[0m`)
|
|
92
|
+
if (table.note) {
|
|
93
|
+
console.log(` ${table.note}`)
|
|
94
|
+
}
|
|
95
|
+
const color = graph.getTableColor(table.name)
|
|
96
|
+
if (color) {
|
|
97
|
+
console.log(` \x1b[2mcolor:\x1b[0m ${color}`)
|
|
98
|
+
}
|
|
99
|
+
const group = graph.getGroup(table.name)
|
|
100
|
+
if (group) {
|
|
101
|
+
console.log(` \x1b[2mgroup:\x1b[0m ${group.name} (${group.tables.length} tables)`)
|
|
102
|
+
}
|
|
103
|
+
console.log()
|
|
104
|
+
console.log(` \x1b[2mColumns:\x1b[0m`)
|
|
105
|
+
for (const col of table.columns) {
|
|
106
|
+
let line = ` ${col.name} \x1b[2m${col.type}\x1b[0m`
|
|
107
|
+
if (col.fk) {
|
|
108
|
+
line += ` \x1b[33m-> ${col.fk.table}.${col.fk.column}\x1b[0m`
|
|
109
|
+
}
|
|
110
|
+
if (col.note) {
|
|
111
|
+
line += `\n \x1b[2m${col.note}\x1b[0m`
|
|
112
|
+
}
|
|
113
|
+
console.log(line)
|
|
114
|
+
}
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case "neighbors": {
|
|
119
|
+
if (!cmdArgs[0]) {
|
|
120
|
+
console.error("Usage: dbml-metaquery <dbml-path> neighbors <table>")
|
|
121
|
+
process.exit(1)
|
|
122
|
+
}
|
|
123
|
+
if (!graph.getTable(cmdArgs[0])) {
|
|
124
|
+
console.error(`Table "${cmdArgs[0]}" not found`)
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const n = graph.getNeighbors(cmdArgs[0])
|
|
129
|
+
console.log(`\x1b[1m${cmdArgs[0]}\x1b[0m\n`)
|
|
130
|
+
|
|
131
|
+
if (n.parents.length > 0) {
|
|
132
|
+
console.log(` \x1b[2mParents (tables I reference):\x1b[0m`)
|
|
133
|
+
for (const p of n.parents) {
|
|
134
|
+
console.log(` \x1b[33m${p.via}\x1b[0m -> ${p.table}`)
|
|
135
|
+
}
|
|
136
|
+
console.log()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (n.children.length > 0) {
|
|
140
|
+
console.log(` \x1b[2mChildren (tables that reference me):\x1b[0m`)
|
|
141
|
+
for (const c of n.children) {
|
|
142
|
+
console.log(` ${c.table}.\x1b[33m${c.via}\x1b[0m`)
|
|
143
|
+
}
|
|
144
|
+
console.log()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (n.parents.length === 0 && n.children.length === 0) {
|
|
148
|
+
console.log(" No FK connections.")
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case "refs-to": {
|
|
154
|
+
if (!cmdArgs[0]) {
|
|
155
|
+
console.error("Usage: dbml-metaquery <dbml-path> refs-to <table>")
|
|
156
|
+
process.exit(1)
|
|
157
|
+
}
|
|
158
|
+
const refs = graph.getReferencingTables(cmdArgs[0])
|
|
159
|
+
|
|
160
|
+
if (refs.length === 0) {
|
|
161
|
+
console.log(`No tables reference "${cmdArgs[0]}"`)
|
|
162
|
+
} else {
|
|
163
|
+
console.log(`${refs.length} tables reference \x1b[1m${cmdArgs[0]}\x1b[0m:\n`)
|
|
164
|
+
for (const r of refs) {
|
|
165
|
+
console.log(` ${r.table}.\x1b[33m${r.column}\x1b[0m -> ${cmdArgs[0]}.\x1b[33m${r.myColumn}\x1b[0m`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
case "rels": {
|
|
172
|
+
if (!cmdArgs[0]) {
|
|
173
|
+
console.error("Usage: dbml-metaquery <dbml-path> rels <table>")
|
|
174
|
+
process.exit(1)
|
|
175
|
+
}
|
|
176
|
+
const rels = graph.getRelationships(cmdArgs[0])
|
|
177
|
+
if (rels.length === 0) {
|
|
178
|
+
console.log(`No relationships found for "${cmdArgs[0]}"`)
|
|
179
|
+
} else {
|
|
180
|
+
console.log(`${rels.length} relationships for "${cmdArgs[0]}":\n`)
|
|
181
|
+
for (const r of rels) {
|
|
182
|
+
const direction =
|
|
183
|
+
r.childTable === cmdArgs[0]
|
|
184
|
+
? ` ${r.childTable}.${r.childColumn} -> ${r.parentTable}.${r.parentColumn}`
|
|
185
|
+
: ` ${r.parentTable}.${r.parentColumn} <- ${r.childTable}.${r.childColumn}`
|
|
186
|
+
console.log(direction)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
break
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case "search": {
|
|
193
|
+
if (!cmdArgs[0]) {
|
|
194
|
+
console.error("Usage: dbml-metaquery <dbml-path> search <query>")
|
|
195
|
+
process.exit(1)
|
|
196
|
+
}
|
|
197
|
+
const results = graph.searchSchema(cmdArgs[0])
|
|
198
|
+
|
|
199
|
+
if (results.length === 0) {
|
|
200
|
+
console.log(`No matches for "${cmdArgs[0]}"`)
|
|
201
|
+
} else {
|
|
202
|
+
console.log(`${results.length} matches for "\x1b[1m${cmdArgs[0]}\x1b[0m":\n`)
|
|
203
|
+
for (const r of results) {
|
|
204
|
+
switch (r.match) {
|
|
205
|
+
case "table_name":
|
|
206
|
+
console.log(` \x1b[1m${r.table}\x1b[0m \x1b[2m(table)\x1b[0m`)
|
|
207
|
+
break
|
|
208
|
+
case "table_note":
|
|
209
|
+
console.log(` \x1b[1m${r.table}\x1b[0m \x1b[2m(note: ${r.text})\x1b[0m`)
|
|
210
|
+
break
|
|
211
|
+
case "column_name":
|
|
212
|
+
console.log(` ${r.table}.\x1b[33m${r.column}\x1b[0m \x1b[2m(column)\x1b[0m`)
|
|
213
|
+
break
|
|
214
|
+
case "column_note":
|
|
215
|
+
console.log(` ${r.table}.\x1b[33m${r.column}\x1b[0m \x1b[2m(note: ${r.text})\x1b[0m`)
|
|
216
|
+
break
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
case "find-path": {
|
|
224
|
+
const [fromTable, toTable] = cmdArgs
|
|
225
|
+
if (!fromTable || !toTable) {
|
|
226
|
+
console.error("Usage: dbml-metaquery <dbml-path> find-path <from> <to>")
|
|
227
|
+
process.exit(1)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const path = graph.findPath(fromTable, toTable)
|
|
232
|
+
|
|
233
|
+
if (path === null) {
|
|
234
|
+
console.log(`No path found from "${fromTable}" to "${toTable}"`)
|
|
235
|
+
process.exit(1)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (path.length === 0) {
|
|
239
|
+
console.log(`"${fromTable}" and "${toTable}" are the same table.`)
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log(
|
|
244
|
+
`Path from \x1b[1m${fromTable}\x1b[0m to \x1b[1m${toTable}\x1b[0m (${path.length} hop${path.length > 1 ? "s" : ""}):\n`,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
for (const step of path) {
|
|
248
|
+
console.log(
|
|
249
|
+
` ${step.from.table}.\x1b[33m${step.from.column}\x1b[0m -> ${step.to.table}.\x1b[33m${step.to.column}\x1b[0m`,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log()
|
|
254
|
+
console.log("\x1b[2mSQL:\x1b[0m")
|
|
255
|
+
console.log(` \x1b[2mFROM\x1b[0m ${fromTable}`)
|
|
256
|
+
for (const step of path) {
|
|
257
|
+
console.log(
|
|
258
|
+
` \x1b[2mJOIN\x1b[0m ${step.to.table} \x1b[2mON\x1b[0m ${step.from.table}.${step.from.column} = ${step.to.table}.${step.to.column}`,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.error(err instanceof Error ? err.message : String(err))
|
|
263
|
+
process.exit(1)
|
|
264
|
+
}
|
|
265
|
+
break
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
default:
|
|
269
|
+
console.error(`Unknown command: ${command}. Use --help for usage.`)
|
|
270
|
+
process.exit(1)
|
|
271
|
+
}
|