@berthojoris/mcp-mysql-server 1.14.1 → 1.16.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/CHANGELOG.md +37 -0
- package/DOCUMENTATIONS.md +366 -8
- package/README.md +21 -14
- package/dist/config/featureConfig.d.ts +6 -1
- package/dist/config/featureConfig.js +93 -0
- package/dist/index.d.ts +233 -0
- package/dist/index.js +67 -0
- package/dist/mcp-server.js +293 -0
- package/dist/tools/documentationGeneratorTools.d.ts +145 -0
- package/dist/tools/documentationGeneratorTools.js +820 -0
- package/dist/tools/intelligentQueryTools.d.ts +94 -0
- package/dist/tools/intelligentQueryTools.js +713 -0
- package/dist/tools/smartDiscoveryTools.d.ts +163 -0
- package/dist/tools/smartDiscoveryTools.js +750 -0
- package/dist/tools/utilityTools.d.ts +11 -0
- package/dist/tools/utilityTools.js +71 -0
- package/package.json +2 -2
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DocumentationGeneratorTools = void 0;
|
|
7
|
+
const connection_1 = __importDefault(require("../db/connection"));
|
|
8
|
+
const config_1 = require("../config/config");
|
|
9
|
+
/**
|
|
10
|
+
* AI-Powered Documentation Generator
|
|
11
|
+
* Automatic database documentation generation with business glossary
|
|
12
|
+
*/
|
|
13
|
+
class DocumentationGeneratorTools {
|
|
14
|
+
constructor(security) {
|
|
15
|
+
this.db = connection_1.default.getInstance();
|
|
16
|
+
this.security = security;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validate database access - ensures only the connected database can be accessed
|
|
20
|
+
*/
|
|
21
|
+
validateDatabaseAccess(requestedDatabase) {
|
|
22
|
+
const connectedDatabase = config_1.dbConfig.database;
|
|
23
|
+
if (!connectedDatabase) {
|
|
24
|
+
return {
|
|
25
|
+
valid: false,
|
|
26
|
+
database: "",
|
|
27
|
+
error: "No database specified in connection string. Cannot access any database.",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (!requestedDatabase) {
|
|
31
|
+
return {
|
|
32
|
+
valid: true,
|
|
33
|
+
database: connectedDatabase,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (requestedDatabase !== connectedDatabase) {
|
|
37
|
+
return {
|
|
38
|
+
valid: false,
|
|
39
|
+
database: "",
|
|
40
|
+
error: `Access denied. You can only access the connected database '${connectedDatabase}'. Requested database '${requestedDatabase}' is not allowed.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
valid: true,
|
|
45
|
+
database: connectedDatabase,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate comprehensive database documentation
|
|
50
|
+
*/
|
|
51
|
+
async generateDocumentation(params) {
|
|
52
|
+
try {
|
|
53
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
54
|
+
if (!dbValidation.valid) {
|
|
55
|
+
return { status: "error", error: dbValidation.error };
|
|
56
|
+
}
|
|
57
|
+
const { scope = "database", table_name, include_business_glossary = true, format = "markdown", include_examples = true, include_statistics = true, } = params;
|
|
58
|
+
const database = dbValidation.database;
|
|
59
|
+
// Validate table_name if provided
|
|
60
|
+
if (table_name && !this.security.validateIdentifier(table_name).valid) {
|
|
61
|
+
return { status: "error", error: "Invalid table name" };
|
|
62
|
+
}
|
|
63
|
+
// Gather schema information
|
|
64
|
+
let tables = [];
|
|
65
|
+
if (scope === "table" && table_name) {
|
|
66
|
+
tables = await this.db.query(`SELECT TABLE_NAME, TABLE_COMMENT, ENGINE, TABLE_ROWS, CREATE_TIME, UPDATE_TIME
|
|
67
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
68
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND TABLE_TYPE = 'BASE TABLE'`, [database, table_name]);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
tables = await this.db.query(`SELECT TABLE_NAME, TABLE_COMMENT, ENGINE, TABLE_ROWS, CREATE_TIME, UPDATE_TIME
|
|
72
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
73
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'
|
|
74
|
+
ORDER BY TABLE_NAME`, [database]);
|
|
75
|
+
}
|
|
76
|
+
if (tables.length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
status: "error",
|
|
79
|
+
error: scope === "table" && table_name
|
|
80
|
+
? `Table '${table_name}' not found`
|
|
81
|
+
: "No tables found in the database",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const tableNames = tables.map((t) => t.TABLE_NAME);
|
|
85
|
+
const placeholders = tableNames.map(() => "?").join(",");
|
|
86
|
+
// Get columns
|
|
87
|
+
const columns = await this.db.query(`SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
|
|
88
|
+
COLUMN_DEFAULT, EXTRA, COLUMN_COMMENT, CHARACTER_MAXIMUM_LENGTH
|
|
89
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
90
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (${placeholders})
|
|
91
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION`, [database, ...tableNames]);
|
|
92
|
+
// Get foreign keys
|
|
93
|
+
const foreignKeys = await this.db.query(`SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME,
|
|
94
|
+
REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
95
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
96
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (${placeholders})
|
|
97
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL`, [database, ...tableNames]);
|
|
98
|
+
// Get indexes
|
|
99
|
+
const indexes = await this.db.query(`SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, NON_UNIQUE, SEQ_IN_INDEX
|
|
100
|
+
FROM INFORMATION_SCHEMA.STATISTICS
|
|
101
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (${placeholders})
|
|
102
|
+
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX`, [database, ...tableNames]);
|
|
103
|
+
// Generate business glossary
|
|
104
|
+
let businessGlossary = new Map();
|
|
105
|
+
if (include_business_glossary) {
|
|
106
|
+
businessGlossary = this.generateBusinessGlossary(columns);
|
|
107
|
+
}
|
|
108
|
+
// Get sample data and statistics if requested
|
|
109
|
+
let tableStats = new Map();
|
|
110
|
+
if (include_statistics) {
|
|
111
|
+
for (const table of tables) {
|
|
112
|
+
try {
|
|
113
|
+
const stats = await this.getTableStatistics(database, table.TABLE_NAME);
|
|
114
|
+
tableStats.set(table.TABLE_NAME, stats);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Skip if error
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Generate documentation
|
|
122
|
+
let content = "";
|
|
123
|
+
switch (format) {
|
|
124
|
+
case "markdown":
|
|
125
|
+
content = this.generateMarkdown(database, tables, columns, foreignKeys, indexes, businessGlossary, tableStats, include_examples);
|
|
126
|
+
break;
|
|
127
|
+
case "html":
|
|
128
|
+
content = this.generateHTML(database, tables, columns, foreignKeys, indexes, businessGlossary, tableStats, include_examples);
|
|
129
|
+
break;
|
|
130
|
+
case "json":
|
|
131
|
+
content = this.generateJSON(database, tables, columns, foreignKeys, indexes, businessGlossary, tableStats);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
status: "success",
|
|
136
|
+
data: {
|
|
137
|
+
format,
|
|
138
|
+
scope,
|
|
139
|
+
content,
|
|
140
|
+
metadata: {
|
|
141
|
+
generated_at: new Date().toISOString(),
|
|
142
|
+
database,
|
|
143
|
+
tables_documented: tables.length,
|
|
144
|
+
columns_documented: columns.length,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
status: "error",
|
|
152
|
+
error: error.message,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Generate data dictionary for a specific table
|
|
158
|
+
*/
|
|
159
|
+
async generateDataDictionary(params) {
|
|
160
|
+
try {
|
|
161
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
162
|
+
if (!dbValidation.valid) {
|
|
163
|
+
return { status: "error", error: dbValidation.error };
|
|
164
|
+
}
|
|
165
|
+
const { table_name, include_sample_values = true, include_constraints = true, } = params;
|
|
166
|
+
const database = dbValidation.database;
|
|
167
|
+
// Validate table name
|
|
168
|
+
if (!this.security.validateIdentifier(table_name).valid) {
|
|
169
|
+
return { status: "error", error: "Invalid table name" };
|
|
170
|
+
}
|
|
171
|
+
// Get table info
|
|
172
|
+
const tableInfo = await this.db.query(`SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES
|
|
173
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, [database, table_name]);
|
|
174
|
+
if (tableInfo.length === 0) {
|
|
175
|
+
return { status: "error", error: `Table '${table_name}' not found` };
|
|
176
|
+
}
|
|
177
|
+
// Get columns
|
|
178
|
+
const columnsResult = await this.db.query(`SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
|
|
179
|
+
COLUMN_DEFAULT, EXTRA, COLUMN_COMMENT
|
|
180
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
181
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
182
|
+
ORDER BY ORDINAL_POSITION`, [database, table_name]);
|
|
183
|
+
// Get foreign keys
|
|
184
|
+
const fkResult = await this.db.query(`SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
185
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
186
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL`, [database, table_name]);
|
|
187
|
+
// Get indexes
|
|
188
|
+
const indexResult = await this.db.query(`SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE
|
|
189
|
+
FROM INFORMATION_SCHEMA.STATISTICS
|
|
190
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
191
|
+
ORDER BY INDEX_NAME, SEQ_IN_INDEX`, [database, table_name]);
|
|
192
|
+
// Get check constraints (MySQL 8.0.16+)
|
|
193
|
+
let checkConstraints = [];
|
|
194
|
+
if (include_constraints) {
|
|
195
|
+
try {
|
|
196
|
+
checkConstraints = await this.db.query(`SELECT CONSTRAINT_NAME, CHECK_CLAUSE
|
|
197
|
+
FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS
|
|
198
|
+
WHERE CONSTRAINT_SCHEMA = ?`, [database]);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Ignore if check constraints are not supported
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Build column information
|
|
205
|
+
const columns = await Promise.all(columnsResult.map(async (col) => {
|
|
206
|
+
const constraints = [];
|
|
207
|
+
if (col.COLUMN_KEY === "PRI")
|
|
208
|
+
constraints.push("PRIMARY KEY");
|
|
209
|
+
if (col.COLUMN_KEY === "UNI")
|
|
210
|
+
constraints.push("UNIQUE");
|
|
211
|
+
if (col.IS_NULLABLE === "NO")
|
|
212
|
+
constraints.push("NOT NULL");
|
|
213
|
+
if (col.EXTRA.includes("auto_increment"))
|
|
214
|
+
constraints.push("AUTO_INCREMENT");
|
|
215
|
+
// Check for foreign key
|
|
216
|
+
const fk = fkResult.find((f) => f.COLUMN_NAME === col.COLUMN_NAME);
|
|
217
|
+
if (fk)
|
|
218
|
+
constraints.push(`FOREIGN KEY -> ${fk.REFERENCED_TABLE_NAME}`);
|
|
219
|
+
// Get sample values
|
|
220
|
+
let sampleValues;
|
|
221
|
+
if (include_sample_values) {
|
|
222
|
+
try {
|
|
223
|
+
const samples = await this.db.query(`SELECT DISTINCT \`${col.COLUMN_NAME}\`
|
|
224
|
+
FROM \`${database}\`.\`${table_name}\`
|
|
225
|
+
WHERE \`${col.COLUMN_NAME}\` IS NOT NULL
|
|
226
|
+
LIMIT 5`);
|
|
227
|
+
sampleValues = samples.map((s) => s[col.COLUMN_NAME]);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Ignore errors
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
name: col.COLUMN_NAME,
|
|
235
|
+
data_type: col.COLUMN_TYPE,
|
|
236
|
+
description: col.COLUMN_COMMENT ||
|
|
237
|
+
this.inferColumnDescription(col.COLUMN_NAME, col.DATA_TYPE),
|
|
238
|
+
constraints,
|
|
239
|
+
is_nullable: col.IS_NULLABLE === "YES",
|
|
240
|
+
default_value: col.COLUMN_DEFAULT,
|
|
241
|
+
sample_values: sampleValues,
|
|
242
|
+
business_term: this.inferBusinessTerm(col.COLUMN_NAME),
|
|
243
|
+
};
|
|
244
|
+
}));
|
|
245
|
+
// Build primary key
|
|
246
|
+
const primaryKey = columnsResult
|
|
247
|
+
.filter((c) => c.COLUMN_KEY === "PRI")
|
|
248
|
+
.map((c) => c.COLUMN_NAME);
|
|
249
|
+
// Build foreign keys
|
|
250
|
+
const foreignKeys = fkResult.map((fk) => ({
|
|
251
|
+
column: fk.COLUMN_NAME,
|
|
252
|
+
references_table: fk.REFERENCED_TABLE_NAME,
|
|
253
|
+
references_column: fk.REFERENCED_COLUMN_NAME,
|
|
254
|
+
}));
|
|
255
|
+
// Build indexes (group by index name)
|
|
256
|
+
const indexMap = new Map();
|
|
257
|
+
for (const idx of indexResult) {
|
|
258
|
+
if (!indexMap.has(idx.INDEX_NAME)) {
|
|
259
|
+
indexMap.set(idx.INDEX_NAME, {
|
|
260
|
+
columns: [],
|
|
261
|
+
is_unique: idx.NON_UNIQUE === 0,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
indexMap.get(idx.INDEX_NAME).columns.push(idx.COLUMN_NAME);
|
|
265
|
+
}
|
|
266
|
+
const indexes = Array.from(indexMap.entries()).map(([name, info]) => ({
|
|
267
|
+
name,
|
|
268
|
+
columns: info.columns,
|
|
269
|
+
is_unique: info.is_unique,
|
|
270
|
+
}));
|
|
271
|
+
return {
|
|
272
|
+
status: "success",
|
|
273
|
+
data: {
|
|
274
|
+
table_name,
|
|
275
|
+
description: tableInfo[0].TABLE_COMMENT ||
|
|
276
|
+
this.inferTableDescription(table_name),
|
|
277
|
+
columns,
|
|
278
|
+
primary_key: primaryKey,
|
|
279
|
+
foreign_keys: foreignKeys,
|
|
280
|
+
indexes,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
return {
|
|
286
|
+
status: "error",
|
|
287
|
+
error: error.message,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Generate a business glossary from the schema
|
|
293
|
+
*/
|
|
294
|
+
async generateBusinessGlossaryReport(params) {
|
|
295
|
+
try {
|
|
296
|
+
const dbValidation = this.validateDatabaseAccess(params?.database);
|
|
297
|
+
if (!dbValidation.valid) {
|
|
298
|
+
return { status: "error", error: dbValidation.error };
|
|
299
|
+
}
|
|
300
|
+
const { include_descriptions = true, group_by = "category", } = params;
|
|
301
|
+
const database = dbValidation.database;
|
|
302
|
+
// Get all columns
|
|
303
|
+
const columns = await this.db.query(`SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT
|
|
304
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
305
|
+
WHERE TABLE_SCHEMA = ?
|
|
306
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION`, [database]);
|
|
307
|
+
const glossary = [];
|
|
308
|
+
const categorySet = new Set();
|
|
309
|
+
const termsByName = new Map();
|
|
310
|
+
for (const col of columns) {
|
|
311
|
+
const businessTerm = this.inferBusinessTerm(col.COLUMN_NAME);
|
|
312
|
+
const category = this.inferCategory(col.COLUMN_NAME, col.DATA_TYPE);
|
|
313
|
+
const description = col.COLUMN_COMMENT ||
|
|
314
|
+
(include_descriptions
|
|
315
|
+
? this.inferColumnDescription(col.COLUMN_NAME, col.DATA_TYPE)
|
|
316
|
+
: "");
|
|
317
|
+
categorySet.add(category);
|
|
318
|
+
// Track related terms (same normalized name across tables)
|
|
319
|
+
const normalizedName = this.normalizeColumnName(col.COLUMN_NAME);
|
|
320
|
+
if (!termsByName.has(normalizedName)) {
|
|
321
|
+
termsByName.set(normalizedName, []);
|
|
322
|
+
}
|
|
323
|
+
termsByName
|
|
324
|
+
.get(normalizedName)
|
|
325
|
+
.push(`${col.TABLE_NAME}.${col.COLUMN_NAME}`);
|
|
326
|
+
glossary.push({
|
|
327
|
+
term: businessTerm,
|
|
328
|
+
technical_name: col.COLUMN_NAME,
|
|
329
|
+
source_table: col.TABLE_NAME,
|
|
330
|
+
data_type: col.DATA_TYPE,
|
|
331
|
+
description,
|
|
332
|
+
category,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// Add related terms
|
|
336
|
+
for (const entry of glossary) {
|
|
337
|
+
const normalizedName = this.normalizeColumnName(entry.technical_name);
|
|
338
|
+
const related = termsByName.get(normalizedName) || [];
|
|
339
|
+
if (related.length > 1) {
|
|
340
|
+
entry.related_terms = related.filter((r) => r !== `${entry.source_table}.${entry.technical_name}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Sort based on group_by
|
|
344
|
+
if (group_by === "alphabetical") {
|
|
345
|
+
glossary.sort((a, b) => a.term.localeCompare(b.term));
|
|
346
|
+
}
|
|
347
|
+
else if (group_by === "category") {
|
|
348
|
+
glossary.sort((a, b) => {
|
|
349
|
+
const catCompare = a.category.localeCompare(b.category);
|
|
350
|
+
if (catCompare !== 0)
|
|
351
|
+
return catCompare;
|
|
352
|
+
return a.term.localeCompare(b.term);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else if (group_by === "table") {
|
|
356
|
+
glossary.sort((a, b) => {
|
|
357
|
+
const tableCompare = a.source_table.localeCompare(b.source_table);
|
|
358
|
+
if (tableCompare !== 0)
|
|
359
|
+
return tableCompare;
|
|
360
|
+
return a.term.localeCompare(b.term);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
status: "success",
|
|
365
|
+
data: {
|
|
366
|
+
glossary,
|
|
367
|
+
categories: Array.from(categorySet).sort(),
|
|
368
|
+
total_terms: glossary.length,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
return {
|
|
374
|
+
status: "error",
|
|
375
|
+
error: error.message,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// ==================== Private Helper Methods ====================
|
|
380
|
+
/**
|
|
381
|
+
* Generate business glossary from columns
|
|
382
|
+
*/
|
|
383
|
+
generateBusinessGlossary(columns) {
|
|
384
|
+
const glossary = new Map();
|
|
385
|
+
for (const col of columns) {
|
|
386
|
+
const term = this.inferBusinessTerm(col.COLUMN_NAME);
|
|
387
|
+
const description = col.COLUMN_COMMENT ||
|
|
388
|
+
this.inferColumnDescription(col.COLUMN_NAME, col.DATA_TYPE);
|
|
389
|
+
glossary.set(col.COLUMN_NAME, `${term}: ${description}`);
|
|
390
|
+
}
|
|
391
|
+
return glossary;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get table statistics
|
|
395
|
+
*/
|
|
396
|
+
async getTableStatistics(database, tableName) {
|
|
397
|
+
const result = await this.db.query(`SELECT COUNT(*) as row_count FROM \`${database}\`.\`${tableName}\``);
|
|
398
|
+
return {
|
|
399
|
+
row_count: result[0]?.row_count || 0,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Generate Markdown documentation
|
|
404
|
+
*/
|
|
405
|
+
generateMarkdown(database, tables, columns, foreignKeys, indexes, businessGlossary, tableStats, includeExamples) {
|
|
406
|
+
const lines = [];
|
|
407
|
+
lines.push(`# Database Documentation: ${database}`);
|
|
408
|
+
lines.push("");
|
|
409
|
+
lines.push(`*Generated on ${new Date().toISOString()}*`);
|
|
410
|
+
lines.push("");
|
|
411
|
+
lines.push("## Table of Contents");
|
|
412
|
+
lines.push("");
|
|
413
|
+
for (const table of tables) {
|
|
414
|
+
lines.push(`- [${table.TABLE_NAME}](#${table.TABLE_NAME.toLowerCase()})`);
|
|
415
|
+
}
|
|
416
|
+
lines.push("");
|
|
417
|
+
// Relationships overview
|
|
418
|
+
if (foreignKeys.length > 0) {
|
|
419
|
+
lines.push("## Relationships");
|
|
420
|
+
lines.push("");
|
|
421
|
+
lines.push("```mermaid");
|
|
422
|
+
lines.push("erDiagram");
|
|
423
|
+
for (const fk of foreignKeys) {
|
|
424
|
+
lines.push(` ${fk.TABLE_NAME} ||--o{ ${fk.REFERENCED_TABLE_NAME} : "${fk.COLUMN_NAME}"`);
|
|
425
|
+
}
|
|
426
|
+
lines.push("```");
|
|
427
|
+
lines.push("");
|
|
428
|
+
}
|
|
429
|
+
// Table documentation
|
|
430
|
+
for (const table of tables) {
|
|
431
|
+
lines.push(`## ${table.TABLE_NAME}`);
|
|
432
|
+
lines.push("");
|
|
433
|
+
if (table.TABLE_COMMENT) {
|
|
434
|
+
lines.push(`> ${table.TABLE_COMMENT}`);
|
|
435
|
+
lines.push("");
|
|
436
|
+
}
|
|
437
|
+
const stats = tableStats.get(table.TABLE_NAME);
|
|
438
|
+
if (stats) {
|
|
439
|
+
lines.push(`**Rows:** ${stats.row_count} | **Engine:** ${table.ENGINE || "N/A"}`);
|
|
440
|
+
lines.push("");
|
|
441
|
+
}
|
|
442
|
+
// Columns table
|
|
443
|
+
lines.push("### Columns");
|
|
444
|
+
lines.push("");
|
|
445
|
+
lines.push("| Column | Type | Nullable | Key | Description |");
|
|
446
|
+
lines.push("|--------|------|----------|-----|-------------|");
|
|
447
|
+
const tableCols = columns.filter((c) => c.TABLE_NAME === table.TABLE_NAME);
|
|
448
|
+
for (const col of tableCols) {
|
|
449
|
+
const keyBadge = col.COLUMN_KEY === "PRI"
|
|
450
|
+
? "🔑 PK"
|
|
451
|
+
: col.COLUMN_KEY === "UNI"
|
|
452
|
+
? "🔒 UNI"
|
|
453
|
+
: col.COLUMN_KEY === "MUL"
|
|
454
|
+
? "🔗 FK"
|
|
455
|
+
: "";
|
|
456
|
+
const description = col.COLUMN_COMMENT ||
|
|
457
|
+
businessGlossary.get(col.COLUMN_NAME)?.split(": ")[1] ||
|
|
458
|
+
"-";
|
|
459
|
+
lines.push(`| \`${col.COLUMN_NAME}\` | ${col.COLUMN_TYPE} | ${col.IS_NULLABLE} | ${keyBadge} | ${description} |`);
|
|
460
|
+
}
|
|
461
|
+
lines.push("");
|
|
462
|
+
// Foreign keys
|
|
463
|
+
const tableFKs = foreignKeys.filter((fk) => fk.TABLE_NAME === table.TABLE_NAME);
|
|
464
|
+
if (tableFKs.length > 0) {
|
|
465
|
+
lines.push("### Foreign Keys");
|
|
466
|
+
lines.push("");
|
|
467
|
+
for (const fk of tableFKs) {
|
|
468
|
+
lines.push(`- \`${fk.COLUMN_NAME}\` → \`${fk.REFERENCED_TABLE_NAME}.${fk.REFERENCED_COLUMN_NAME}\``);
|
|
469
|
+
}
|
|
470
|
+
lines.push("");
|
|
471
|
+
}
|
|
472
|
+
// Indexes
|
|
473
|
+
const tableIndexes = indexes.filter((idx) => idx.TABLE_NAME === table.TABLE_NAME);
|
|
474
|
+
if (tableIndexes.length > 0) {
|
|
475
|
+
lines.push("### Indexes");
|
|
476
|
+
lines.push("");
|
|
477
|
+
const indexGroups = new Map();
|
|
478
|
+
for (const idx of tableIndexes) {
|
|
479
|
+
if (!indexGroups.has(idx.INDEX_NAME)) {
|
|
480
|
+
indexGroups.set(idx.INDEX_NAME, []);
|
|
481
|
+
}
|
|
482
|
+
indexGroups.get(idx.INDEX_NAME).push(idx.COLUMN_NAME);
|
|
483
|
+
}
|
|
484
|
+
for (const [indexName, cols] of indexGroups) {
|
|
485
|
+
const isUnique = tableIndexes.find((i) => i.INDEX_NAME === indexName)?.NON_UNIQUE === 0;
|
|
486
|
+
lines.push(`- **${indexName}**${isUnique ? " (UNIQUE)" : ""}: ${cols.join(", ")}`);
|
|
487
|
+
}
|
|
488
|
+
lines.push("");
|
|
489
|
+
}
|
|
490
|
+
// Example queries
|
|
491
|
+
if (includeExamples) {
|
|
492
|
+
lines.push("### Example Queries");
|
|
493
|
+
lines.push("");
|
|
494
|
+
lines.push("```sql");
|
|
495
|
+
lines.push(`-- Select all from ${table.TABLE_NAME}`);
|
|
496
|
+
lines.push(`SELECT * FROM \`${database}\`.\`${table.TABLE_NAME}\` LIMIT 10;`);
|
|
497
|
+
lines.push("");
|
|
498
|
+
lines.push(`-- Count records`);
|
|
499
|
+
lines.push(`SELECT COUNT(*) FROM \`${database}\`.\`${table.TABLE_NAME}\`;`);
|
|
500
|
+
lines.push("```");
|
|
501
|
+
lines.push("");
|
|
502
|
+
}
|
|
503
|
+
lines.push("---");
|
|
504
|
+
lines.push("");
|
|
505
|
+
}
|
|
506
|
+
// Business glossary
|
|
507
|
+
if (businessGlossary.size > 0) {
|
|
508
|
+
lines.push("## Business Glossary");
|
|
509
|
+
lines.push("");
|
|
510
|
+
lines.push("| Term | Description |");
|
|
511
|
+
lines.push("|------|-------------|");
|
|
512
|
+
const sortedTerms = Array.from(businessGlossary.entries()).sort((a, b) => a[0].localeCompare(b[0]));
|
|
513
|
+
for (const [term, desc] of sortedTerms.slice(0, 50)) {
|
|
514
|
+
lines.push(`| ${term} | ${desc} |`);
|
|
515
|
+
}
|
|
516
|
+
lines.push("");
|
|
517
|
+
}
|
|
518
|
+
return lines.join("\n");
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Generate HTML documentation
|
|
522
|
+
*/
|
|
523
|
+
generateHTML(database, tables, columns, foreignKeys, indexes, businessGlossary, tableStats, includeExamples) {
|
|
524
|
+
const html = [];
|
|
525
|
+
html.push("<!DOCTYPE html>");
|
|
526
|
+
html.push('<html lang="en">');
|
|
527
|
+
html.push("<head>");
|
|
528
|
+
html.push(' <meta charset="UTF-8">');
|
|
529
|
+
html.push(' <meta name="viewport" content="width=device-width, initial-scale=1.0">');
|
|
530
|
+
html.push(` <title>Database Documentation: ${database}</title>`);
|
|
531
|
+
html.push(" <style>");
|
|
532
|
+
html.push(" body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }");
|
|
533
|
+
html.push(" h1 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }");
|
|
534
|
+
html.push(" h2 { color: #34495e; margin-top: 30px; }");
|
|
535
|
+
html.push(" h3 { color: #7f8c8d; }");
|
|
536
|
+
html.push(" table { width: 100%; border-collapse: collapse; margin: 15px 0; }");
|
|
537
|
+
html.push(" th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }");
|
|
538
|
+
html.push(" th { background: #3498db; color: white; }");
|
|
539
|
+
html.push(" tr:nth-child(even) { background: #f9f9f9; }");
|
|
540
|
+
html.push(" code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }");
|
|
541
|
+
html.push(" pre { background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 5px; overflow-x: auto; }");
|
|
542
|
+
html.push(" .badge { display: inline-block; padding: 3px 8px; border-radius: 3px; font-size: 12px; }");
|
|
543
|
+
html.push(" .badge-pk { background: #e74c3c; color: white; }");
|
|
544
|
+
html.push(" .badge-fk { background: #3498db; color: white; }");
|
|
545
|
+
html.push(" .badge-uni { background: #9b59b6; color: white; }");
|
|
546
|
+
html.push(" .toc { background: #f8f9fa; padding: 15px; border-radius: 5px; }");
|
|
547
|
+
html.push(" .toc a { color: #3498db; text-decoration: none; }");
|
|
548
|
+
html.push(" .toc a:hover { text-decoration: underline; }");
|
|
549
|
+
html.push(" </style>");
|
|
550
|
+
html.push("</head>");
|
|
551
|
+
html.push("<body>");
|
|
552
|
+
html.push(` <h1>📊 Database Documentation: ${database}</h1>`);
|
|
553
|
+
html.push(` <p><em>Generated on ${new Date().toISOString()}</em></p>`);
|
|
554
|
+
// Table of contents
|
|
555
|
+
html.push(' <div class="toc">');
|
|
556
|
+
html.push(" <h3>📑 Table of Contents</h3>");
|
|
557
|
+
html.push(" <ul>");
|
|
558
|
+
for (const table of tables) {
|
|
559
|
+
html.push(` <li><a href="#${table.TABLE_NAME}">${table.TABLE_NAME}</a></li>`);
|
|
560
|
+
}
|
|
561
|
+
html.push(" </ul>");
|
|
562
|
+
html.push(" </div>");
|
|
563
|
+
// Tables documentation
|
|
564
|
+
for (const table of tables) {
|
|
565
|
+
html.push(` <h2 id="${table.TABLE_NAME}">📋 ${table.TABLE_NAME}</h2>`);
|
|
566
|
+
if (table.TABLE_COMMENT) {
|
|
567
|
+
html.push(` <blockquote>${table.TABLE_COMMENT}</blockquote>`);
|
|
568
|
+
}
|
|
569
|
+
const stats = tableStats.get(table.TABLE_NAME);
|
|
570
|
+
if (stats) {
|
|
571
|
+
html.push(` <p><strong>Rows:</strong> ${stats.row_count} | <strong>Engine:</strong> ${table.ENGINE || "N/A"}</p>`);
|
|
572
|
+
}
|
|
573
|
+
// Columns
|
|
574
|
+
html.push(" <h3>Columns</h3>");
|
|
575
|
+
html.push(" <table>");
|
|
576
|
+
html.push(" <tr><th>Column</th><th>Type</th><th>Nullable</th><th>Key</th><th>Description</th></tr>");
|
|
577
|
+
const tableCols = columns.filter((c) => c.TABLE_NAME === table.TABLE_NAME);
|
|
578
|
+
for (const col of tableCols) {
|
|
579
|
+
let keyBadge = "";
|
|
580
|
+
if (col.COLUMN_KEY === "PRI") {
|
|
581
|
+
keyBadge = '<span class="badge badge-pk">PK</span>';
|
|
582
|
+
}
|
|
583
|
+
else if (col.COLUMN_KEY === "UNI") {
|
|
584
|
+
keyBadge = '<span class="badge badge-uni">UNI</span>';
|
|
585
|
+
}
|
|
586
|
+
else if (col.COLUMN_KEY === "MUL") {
|
|
587
|
+
keyBadge = '<span class="badge badge-fk">FK</span>';
|
|
588
|
+
}
|
|
589
|
+
const description = col.COLUMN_COMMENT ||
|
|
590
|
+
businessGlossary.get(col.COLUMN_NAME)?.split(": ")[1] ||
|
|
591
|
+
"-";
|
|
592
|
+
html.push(` <tr><td><code>${col.COLUMN_NAME}</code></td><td>${col.COLUMN_TYPE}</td><td>${col.IS_NULLABLE}</td><td>${keyBadge}</td><td>${description}</td></tr>`);
|
|
593
|
+
}
|
|
594
|
+
html.push(" </table>");
|
|
595
|
+
// Example queries
|
|
596
|
+
if (includeExamples) {
|
|
597
|
+
html.push(" <h3>Example Queries</h3>");
|
|
598
|
+
html.push(" <pre>");
|
|
599
|
+
html.push(`-- Select all from ${table.TABLE_NAME}`);
|
|
600
|
+
html.push(`SELECT * FROM \`${database}\`.\`${table.TABLE_NAME}\` LIMIT 10;`);
|
|
601
|
+
html.push("");
|
|
602
|
+
html.push("-- Count records");
|
|
603
|
+
html.push(`SELECT COUNT(*) FROM \`${database}\`.\`${table.TABLE_NAME}\`;`);
|
|
604
|
+
html.push(" </pre>");
|
|
605
|
+
}
|
|
606
|
+
html.push(" <hr>");
|
|
607
|
+
}
|
|
608
|
+
html.push("</body>");
|
|
609
|
+
html.push("</html>");
|
|
610
|
+
return html.join("\n");
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Generate JSON documentation
|
|
614
|
+
*/
|
|
615
|
+
generateJSON(database, tables, columns, foreignKeys, indexes, businessGlossary, tableStats) {
|
|
616
|
+
const doc = {
|
|
617
|
+
database,
|
|
618
|
+
generated_at: new Date().toISOString(),
|
|
619
|
+
tables: tables.map((table) => {
|
|
620
|
+
const tableCols = columns.filter((c) => c.TABLE_NAME === table.TABLE_NAME);
|
|
621
|
+
const tableFKs = foreignKeys.filter((fk) => fk.TABLE_NAME === table.TABLE_NAME);
|
|
622
|
+
const tableIdxs = indexes.filter((idx) => idx.TABLE_NAME === table.TABLE_NAME);
|
|
623
|
+
return {
|
|
624
|
+
name: table.TABLE_NAME,
|
|
625
|
+
comment: table.TABLE_COMMENT || null,
|
|
626
|
+
engine: table.ENGINE,
|
|
627
|
+
stats: tableStats.get(table.TABLE_NAME) || null,
|
|
628
|
+
columns: tableCols.map((col) => ({
|
|
629
|
+
name: col.COLUMN_NAME,
|
|
630
|
+
type: col.COLUMN_TYPE,
|
|
631
|
+
data_type: col.DATA_TYPE,
|
|
632
|
+
is_nullable: col.IS_NULLABLE === "YES",
|
|
633
|
+
column_key: col.COLUMN_KEY || null,
|
|
634
|
+
default_value: col.COLUMN_DEFAULT,
|
|
635
|
+
extra: col.EXTRA || null,
|
|
636
|
+
comment: col.COLUMN_COMMENT || null,
|
|
637
|
+
business_term: businessGlossary.get(col.COLUMN_NAME)?.split(": ")[0] || null,
|
|
638
|
+
})),
|
|
639
|
+
foreign_keys: tableFKs.map((fk) => ({
|
|
640
|
+
column: fk.COLUMN_NAME,
|
|
641
|
+
references_table: fk.REFERENCED_TABLE_NAME,
|
|
642
|
+
references_column: fk.REFERENCED_COLUMN_NAME,
|
|
643
|
+
})),
|
|
644
|
+
indexes: this.groupIndexes(tableIdxs),
|
|
645
|
+
};
|
|
646
|
+
}),
|
|
647
|
+
relationships: foreignKeys.map((fk) => ({
|
|
648
|
+
from_table: fk.TABLE_NAME,
|
|
649
|
+
from_column: fk.COLUMN_NAME,
|
|
650
|
+
to_table: fk.REFERENCED_TABLE_NAME,
|
|
651
|
+
to_column: fk.REFERENCED_COLUMN_NAME,
|
|
652
|
+
})),
|
|
653
|
+
};
|
|
654
|
+
return JSON.stringify(doc, null, 2);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Group indexes by name
|
|
658
|
+
*/
|
|
659
|
+
groupIndexes(indexes) {
|
|
660
|
+
const groups = new Map();
|
|
661
|
+
for (const idx of indexes) {
|
|
662
|
+
if (!groups.has(idx.INDEX_NAME)) {
|
|
663
|
+
groups.set(idx.INDEX_NAME, {
|
|
664
|
+
columns: [],
|
|
665
|
+
is_unique: idx.NON_UNIQUE === 0,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
groups.get(idx.INDEX_NAME).columns.push(idx.COLUMN_NAME);
|
|
669
|
+
}
|
|
670
|
+
return Array.from(groups.entries()).map(([name, info]) => ({
|
|
671
|
+
name,
|
|
672
|
+
columns: info.columns,
|
|
673
|
+
is_unique: info.is_unique,
|
|
674
|
+
}));
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Infer business term from column name
|
|
678
|
+
*/
|
|
679
|
+
inferBusinessTerm(columnName) {
|
|
680
|
+
// Convert snake_case or camelCase to Title Case
|
|
681
|
+
return columnName
|
|
682
|
+
.replace(/_/g, " ")
|
|
683
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
684
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Infer column description from name and type
|
|
688
|
+
*/
|
|
689
|
+
inferColumnDescription(columnName, dataType) {
|
|
690
|
+
const name = columnName.toLowerCase();
|
|
691
|
+
// Common patterns
|
|
692
|
+
if (name === "id" || name.endsWith("_id")) {
|
|
693
|
+
return "Unique identifier";
|
|
694
|
+
}
|
|
695
|
+
if (name.includes("created")) {
|
|
696
|
+
return "Record creation timestamp";
|
|
697
|
+
}
|
|
698
|
+
if (name.includes("updated") || name.includes("modified")) {
|
|
699
|
+
return "Last update timestamp";
|
|
700
|
+
}
|
|
701
|
+
if (name.includes("deleted")) {
|
|
702
|
+
return "Soft delete indicator/timestamp";
|
|
703
|
+
}
|
|
704
|
+
if (name.includes("email")) {
|
|
705
|
+
return "Email address";
|
|
706
|
+
}
|
|
707
|
+
if (name.includes("phone") || name.includes("mobile")) {
|
|
708
|
+
return "Phone number";
|
|
709
|
+
}
|
|
710
|
+
if (name.includes("name")) {
|
|
711
|
+
return "Name or title";
|
|
712
|
+
}
|
|
713
|
+
if (name.includes("description") || name.includes("desc")) {
|
|
714
|
+
return "Detailed description";
|
|
715
|
+
}
|
|
716
|
+
if (name.includes("status")) {
|
|
717
|
+
return "Status indicator";
|
|
718
|
+
}
|
|
719
|
+
if (name.includes("type")) {
|
|
720
|
+
return "Type or category";
|
|
721
|
+
}
|
|
722
|
+
if (name.includes("price") || name.includes("amount") || name.includes("cost")) {
|
|
723
|
+
return "Monetary value";
|
|
724
|
+
}
|
|
725
|
+
if (name.includes("count") || name.includes("quantity") || name.includes("qty")) {
|
|
726
|
+
return "Numeric count";
|
|
727
|
+
}
|
|
728
|
+
if (name.includes("is_") || name.includes("has_") || name.startsWith("is") || name.startsWith("has")) {
|
|
729
|
+
return "Boolean flag";
|
|
730
|
+
}
|
|
731
|
+
if (name.includes("url") || name.includes("link")) {
|
|
732
|
+
return "URL or web link";
|
|
733
|
+
}
|
|
734
|
+
if (name.includes("address")) {
|
|
735
|
+
return "Physical or mailing address";
|
|
736
|
+
}
|
|
737
|
+
if (name.includes("date")) {
|
|
738
|
+
return "Date value";
|
|
739
|
+
}
|
|
740
|
+
// Type-based defaults
|
|
741
|
+
if (dataType.includes("text") || dataType.includes("blob")) {
|
|
742
|
+
return "Extended text content";
|
|
743
|
+
}
|
|
744
|
+
if (dataType.includes("int") || dataType.includes("decimal") || dataType.includes("float")) {
|
|
745
|
+
return "Numeric value";
|
|
746
|
+
}
|
|
747
|
+
if (dataType.includes("date") || dataType.includes("time")) {
|
|
748
|
+
return "Date/time value";
|
|
749
|
+
}
|
|
750
|
+
if (dataType.includes("enum") || dataType.includes("set")) {
|
|
751
|
+
return "Enumerated value";
|
|
752
|
+
}
|
|
753
|
+
return "Data field";
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Infer table description from name
|
|
757
|
+
*/
|
|
758
|
+
inferTableDescription(tableName) {
|
|
759
|
+
const name = tableName.toLowerCase();
|
|
760
|
+
if (name.includes("user"))
|
|
761
|
+
return "Stores user account information";
|
|
762
|
+
if (name.includes("order"))
|
|
763
|
+
return "Stores order records";
|
|
764
|
+
if (name.includes("product"))
|
|
765
|
+
return "Stores product catalog";
|
|
766
|
+
if (name.includes("customer"))
|
|
767
|
+
return "Stores customer information";
|
|
768
|
+
if (name.includes("invoice"))
|
|
769
|
+
return "Stores invoice records";
|
|
770
|
+
if (name.includes("payment"))
|
|
771
|
+
return "Stores payment transactions";
|
|
772
|
+
if (name.includes("log"))
|
|
773
|
+
return "Stores activity/event logs";
|
|
774
|
+
if (name.includes("config") || name.includes("setting"))
|
|
775
|
+
return "Stores configuration settings";
|
|
776
|
+
if (name.includes("session"))
|
|
777
|
+
return "Stores session data";
|
|
778
|
+
if (name.includes("cache"))
|
|
779
|
+
return "Stores cached data";
|
|
780
|
+
if (name.includes("migration"))
|
|
781
|
+
return "Stores database migration history";
|
|
782
|
+
return `Stores ${this.inferBusinessTerm(tableName).toLowerCase()} data`;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Infer category from column name and type
|
|
786
|
+
*/
|
|
787
|
+
inferCategory(columnName, dataType) {
|
|
788
|
+
const name = columnName.toLowerCase();
|
|
789
|
+
if (name === "id" || name.endsWith("_id"))
|
|
790
|
+
return "Identifiers";
|
|
791
|
+
if (name.includes("created") || name.includes("updated") || name.includes("deleted") || name.includes("date") || name.includes("time"))
|
|
792
|
+
return "Timestamps";
|
|
793
|
+
if (name.includes("email") || name.includes("phone") || name.includes("address"))
|
|
794
|
+
return "Contact";
|
|
795
|
+
if (name.includes("name") || name.includes("title"))
|
|
796
|
+
return "Names";
|
|
797
|
+
if (name.includes("price") || name.includes("amount") || name.includes("cost") || name.includes("total"))
|
|
798
|
+
return "Financial";
|
|
799
|
+
if (name.includes("status") || name.includes("type") || name.includes("category"))
|
|
800
|
+
return "Classification";
|
|
801
|
+
if (name.startsWith("is_") || name.startsWith("has_"))
|
|
802
|
+
return "Flags";
|
|
803
|
+
if (name.includes("description") || name.includes("notes") || name.includes("comment"))
|
|
804
|
+
return "Text Content";
|
|
805
|
+
if (name.includes("url") || name.includes("link") || name.includes("path"))
|
|
806
|
+
return "References";
|
|
807
|
+
return "General";
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Normalize column name for comparison
|
|
811
|
+
*/
|
|
812
|
+
normalizeColumnName(name) {
|
|
813
|
+
return name
|
|
814
|
+
.toLowerCase()
|
|
815
|
+
.replace(/^(fk_|pk_|idx_)/, "")
|
|
816
|
+
.replace(/_id$/, "")
|
|
817
|
+
.replace(/[_-]/g, "");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
exports.DocumentationGeneratorTools = DocumentationGeneratorTools;
|