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.
@@ -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
+ }