@cuongph.dev/dbdocgen 0.1.0 → 0.1.1
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 +97 -37
- package/dist/cli/index.cjs +1080 -163
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1090 -172
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +1086 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1095 -206
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
package/dist/index.cjs
CHANGED
|
@@ -62,12 +62,17 @@ var reviewTodoSchema = import_zod.z.object({
|
|
|
62
62
|
var columnDocSchema = import_zod.z.object({
|
|
63
63
|
name: import_zod.z.string().min(1),
|
|
64
64
|
type: import_zod.z.string().min(1),
|
|
65
|
+
size: import_zod.z.string().optional(),
|
|
65
66
|
nullable: import_zod.z.boolean(),
|
|
66
67
|
defaultValue: import_zod.z.string().optional(),
|
|
68
|
+
minValue: import_zod.z.string().optional(),
|
|
69
|
+
maxValue: import_zod.z.string().optional(),
|
|
70
|
+
isUnique: import_zod.z.boolean(),
|
|
67
71
|
isPrimaryKey: import_zod.z.boolean(),
|
|
68
72
|
isForeignKey: import_zod.z.boolean(),
|
|
69
73
|
comment: import_zod.z.string().optional(),
|
|
70
|
-
description: enrichedTextSchema.optional()
|
|
74
|
+
description: enrichedTextSchema.optional(),
|
|
75
|
+
constraintNotes: import_zod.z.array(import_zod.z.string()).optional()
|
|
71
76
|
});
|
|
72
77
|
var foreignKeyDocSchema = import_zod.z.object({
|
|
73
78
|
name: import_zod.z.string().optional(),
|
|
@@ -146,7 +151,7 @@ var outputLanguageSchema = import_zod2.z.enum(["en", "jp"]);
|
|
|
146
151
|
var dbdocgenConfigSchema = import_zod2.z.object({
|
|
147
152
|
schema: import_zod2.z.string().default("./schema.sql"),
|
|
148
153
|
dialect: dialectSchema.optional(),
|
|
149
|
-
outDir: import_zod2.z.string().default("./
|
|
154
|
+
outDir: import_zod2.z.string().default("./output"),
|
|
150
155
|
output: import_zod2.z.object({
|
|
151
156
|
formats: import_zod2.z.array(outputFormatSchema).default(["excel", "markdown", "html", "diagram", "word"]),
|
|
152
157
|
language: outputLanguageSchema.default("en")
|
|
@@ -189,6 +194,221 @@ function createWarning(code, message, target) {
|
|
|
189
194
|
};
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
// src/parsers/sql/column-meta.ts
|
|
198
|
+
function normalizeColumnType(definition) {
|
|
199
|
+
if (typeof definition !== "object" || definition === null) {
|
|
200
|
+
return { type: String(definition ?? "unknown").toLowerCase() };
|
|
201
|
+
}
|
|
202
|
+
const def = definition;
|
|
203
|
+
const base = String(def.dataType ?? def.type ?? def.name ?? "unknown").toLowerCase();
|
|
204
|
+
if (String(def.dataType ?? "").toUpperCase() === "ENUM") {
|
|
205
|
+
const values = extractEnumValues(def.expr);
|
|
206
|
+
if (values.length > 0) {
|
|
207
|
+
const joined = values.join(", ");
|
|
208
|
+
return {
|
|
209
|
+
type: `${base}(${values.map((v) => `'${v}'`).join(",")})`,
|
|
210
|
+
size: String(values.length)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (def.length !== void 0 && def.length !== null) {
|
|
215
|
+
const length = formatAstValue(def.length);
|
|
216
|
+
if (def.scale !== void 0 && def.scale !== null) {
|
|
217
|
+
const scale = formatAstValue(def.scale);
|
|
218
|
+
return {
|
|
219
|
+
type: `${base}(${length},${scale})`,
|
|
220
|
+
size: `${length},${scale}`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
type: `${base}(${length})`,
|
|
225
|
+
size: length
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const suffix = Array.isArray(def.suffix) ? def.suffix.map((item) => formatAstValue(item)).filter(Boolean).join(" ") : def.suffix ? formatAstValue(def.suffix) : "";
|
|
229
|
+
return {
|
|
230
|
+
type: suffix ? `${base} ${suffix}`.trim() : base
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function extractColumnComment(definition) {
|
|
234
|
+
const comment = definition.comment;
|
|
235
|
+
if (!comment) return void 0;
|
|
236
|
+
const value = comment.value;
|
|
237
|
+
if (value?.value !== void 0) return String(value.value);
|
|
238
|
+
if (comment.value !== void 0 && typeof comment.value === "string") {
|
|
239
|
+
return comment.value;
|
|
240
|
+
}
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
function hasColumnUnique(definition) {
|
|
244
|
+
return definition.unique === "unique" || definition.unique === true;
|
|
245
|
+
}
|
|
246
|
+
function extractColumnConstraintNotes(definition) {
|
|
247
|
+
const notes = [];
|
|
248
|
+
if (definition.auto_increment === "auto_increment" || definition.auto_increment === true) {
|
|
249
|
+
notes.push("AUTO_INCREMENT");
|
|
250
|
+
}
|
|
251
|
+
if (definition.on_update) {
|
|
252
|
+
notes.push(`ON UPDATE ${formatOnUpdate(definition.on_update)}`);
|
|
253
|
+
}
|
|
254
|
+
const generated = definition.generated;
|
|
255
|
+
if (generated) {
|
|
256
|
+
const storage = String(generated.storage_type ?? "virtual").toUpperCase();
|
|
257
|
+
const expression = stringifyGeneratedExpression(generated.expr);
|
|
258
|
+
notes.push(
|
|
259
|
+
expression ? `GENERATED ALWAYS ${storage}: ${expression}` : `GENERATED ALWAYS ${storage}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const enumValues = extractEnumValues(definition.definition?.expr);
|
|
263
|
+
if (enumValues.length > 0) {
|
|
264
|
+
notes.push(`ENUM: ${enumValues.join(", ")}`);
|
|
265
|
+
}
|
|
266
|
+
return notes;
|
|
267
|
+
}
|
|
268
|
+
function extractEnumValues(expr) {
|
|
269
|
+
if (!expr || typeof expr !== "object") return [];
|
|
270
|
+
const object = expr;
|
|
271
|
+
if (object.type !== "expr_list" || !Array.isArray(object.value)) return [];
|
|
272
|
+
return object.value.map((item) => {
|
|
273
|
+
if (!item || typeof item !== "object") return "";
|
|
274
|
+
const entry = item;
|
|
275
|
+
return entry.value !== void 0 ? String(entry.value) : "";
|
|
276
|
+
}).filter(Boolean);
|
|
277
|
+
}
|
|
278
|
+
function formatOnUpdate(value) {
|
|
279
|
+
if (!value || typeof value !== "object") return String(value ?? "");
|
|
280
|
+
const object = value;
|
|
281
|
+
if (object.type === "function" && object.name) {
|
|
282
|
+
const name = object.name;
|
|
283
|
+
const parts = Array.isArray(name.name) ? name.name : [];
|
|
284
|
+
return parts.map((part) => String(part.value ?? "")).join("") || "CURRENT_TIMESTAMP";
|
|
285
|
+
}
|
|
286
|
+
return formatAstValue(value);
|
|
287
|
+
}
|
|
288
|
+
function stringifyGeneratedExpression(expr) {
|
|
289
|
+
if (!expr || typeof expr !== "object") return void 0;
|
|
290
|
+
const text = stringifyExpression(expr);
|
|
291
|
+
return text === "check" ? void 0 : text;
|
|
292
|
+
}
|
|
293
|
+
function extractCheckBounds(expression, columnName) {
|
|
294
|
+
const result = {};
|
|
295
|
+
walkCheckExpression(expression, columnName, result);
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
function walkCheckExpression(expression, columnName, result) {
|
|
299
|
+
if (!expression || typeof expression !== "object") return;
|
|
300
|
+
const expr = expression;
|
|
301
|
+
if (expr.type === "binary_expr") {
|
|
302
|
+
const operator = String(expr.operator ?? "").toUpperCase();
|
|
303
|
+
if (operator === "AND" || operator === "OR") {
|
|
304
|
+
walkCheckExpression(expr.left, columnName, result);
|
|
305
|
+
walkCheckExpression(expr.right, columnName, result);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const columnRef = findColumnRef(expr.left) ?? findColumnRef(expr.right);
|
|
309
|
+
if (columnRef !== columnName) return;
|
|
310
|
+
const bound = readBound(expr, columnName);
|
|
311
|
+
if (!bound) return;
|
|
312
|
+
if (bound.kind === "min") {
|
|
313
|
+
result.minValue = mergeBound(result.minValue, bound.value, "max");
|
|
314
|
+
} else {
|
|
315
|
+
result.maxValue = mergeBound(result.maxValue, bound.value, "min");
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
result.expression ??= stringifyExpression(expr);
|
|
320
|
+
}
|
|
321
|
+
function readBound(expr, columnName) {
|
|
322
|
+
const operator = String(expr.operator ?? "");
|
|
323
|
+
const left = expr.left;
|
|
324
|
+
const right = expr.right;
|
|
325
|
+
if (findColumnRef(left) === columnName) {
|
|
326
|
+
if (operator === ">=" || operator === ">") {
|
|
327
|
+
return { kind: "min", value: formatAstValue(right) };
|
|
328
|
+
}
|
|
329
|
+
if (operator === "<=" || operator === "<") {
|
|
330
|
+
return { kind: "max", value: formatAstValue(right) };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (findColumnRef(right) === columnName) {
|
|
334
|
+
if (operator === ">=" || operator === ">") {
|
|
335
|
+
return { kind: "max", value: formatAstValue(left) };
|
|
336
|
+
}
|
|
337
|
+
if (operator === "<=" || operator === "<") {
|
|
338
|
+
return { kind: "min", value: formatAstValue(left) };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return void 0;
|
|
342
|
+
}
|
|
343
|
+
function mergeBound(current, next, pick) {
|
|
344
|
+
if (!current) return next;
|
|
345
|
+
const currentNum = Number(current);
|
|
346
|
+
const nextNum = Number(next);
|
|
347
|
+
if (!Number.isNaN(currentNum) && !Number.isNaN(nextNum)) {
|
|
348
|
+
return pick === "min" ? String(Math.max(currentNum, nextNum)) : String(Math.min(currentNum, nextNum));
|
|
349
|
+
}
|
|
350
|
+
return next;
|
|
351
|
+
}
|
|
352
|
+
function findColumnRef(value) {
|
|
353
|
+
if (!value || typeof value !== "object") return void 0;
|
|
354
|
+
const expr = value;
|
|
355
|
+
if (expr.type === "column_ref" && expr.column) {
|
|
356
|
+
return String(expr.column);
|
|
357
|
+
}
|
|
358
|
+
return void 0;
|
|
359
|
+
}
|
|
360
|
+
function formatAstValue(value) {
|
|
361
|
+
if (value === null || value === void 0) return "";
|
|
362
|
+
if (typeof value === "object") {
|
|
363
|
+
const object = value;
|
|
364
|
+
if (object.value !== void 0) return String(object.value);
|
|
365
|
+
if (object.dataType) return normalizeColumnType(object).type;
|
|
366
|
+
}
|
|
367
|
+
return String(value);
|
|
368
|
+
}
|
|
369
|
+
function stringifyExpression(expr) {
|
|
370
|
+
if (expr.type === "binary_expr") {
|
|
371
|
+
const left = stringifyExpression(expr.left ?? {});
|
|
372
|
+
const right = stringifyExpression(expr.right ?? {});
|
|
373
|
+
return `${left} ${expr.operator} ${right}`.trim();
|
|
374
|
+
}
|
|
375
|
+
if (expr.type === "column_ref") return String(expr.column ?? "");
|
|
376
|
+
if (expr.value !== void 0) return formatAstValue(expr);
|
|
377
|
+
return "check";
|
|
378
|
+
}
|
|
379
|
+
function extractConstraintColumnNames(definition) {
|
|
380
|
+
return extractDeepColumnNames(definition);
|
|
381
|
+
}
|
|
382
|
+
function extractDeepColumnNames(value) {
|
|
383
|
+
if (!Array.isArray(value)) return [];
|
|
384
|
+
return value.map((item) => {
|
|
385
|
+
if (typeof item !== "object" || item === null) return String(item ?? "unknown");
|
|
386
|
+
const object = item;
|
|
387
|
+
if (object.column !== void 0) return String(object.column);
|
|
388
|
+
if (object.expr) return extractDeepColumnName(object.expr);
|
|
389
|
+
return String(object.name ?? "unknown");
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function extractDeepColumnName(value) {
|
|
393
|
+
if (typeof value !== "object" || value === null) {
|
|
394
|
+
return String(value ?? "unknown");
|
|
395
|
+
}
|
|
396
|
+
const object = value;
|
|
397
|
+
if (object.expr) return extractDeepColumnName(object.expr);
|
|
398
|
+
if (object.column && typeof object.column === "object") {
|
|
399
|
+
return extractDeepColumnName(object.column);
|
|
400
|
+
}
|
|
401
|
+
if (object.column !== void 0) return String(object.column);
|
|
402
|
+
return String(object.name ?? "unknown");
|
|
403
|
+
}
|
|
404
|
+
function stringifyCheckDefinition(definition) {
|
|
405
|
+
if (!Array.isArray(definition) || definition.length === 0) return void 0;
|
|
406
|
+
if (definition.length === 1) {
|
|
407
|
+
return stringifyExpression(definition[0]);
|
|
408
|
+
}
|
|
409
|
+
return definition.map((item) => stringifyExpression(item)).filter(Boolean).join("; ");
|
|
410
|
+
}
|
|
411
|
+
|
|
192
412
|
// src/parsers/sql/sql-normalizer.ts
|
|
193
413
|
function normalizeSqlAst(ast, dialect) {
|
|
194
414
|
const statements = Array.isArray(ast) ? ast : [ast];
|
|
@@ -210,7 +430,11 @@ function normalizeSqlAst(ast, dialect) {
|
|
|
210
430
|
}
|
|
211
431
|
for (const index of indexes) {
|
|
212
432
|
const table = tables.find((candidate) => candidate.name === index.table);
|
|
213
|
-
table
|
|
433
|
+
if (!table) continue;
|
|
434
|
+
table.indexes.push(index);
|
|
435
|
+
if (index.unique) {
|
|
436
|
+
markColumnsUnique(table, index.columns, `INDEX ${index.name}`);
|
|
437
|
+
}
|
|
214
438
|
}
|
|
215
439
|
return {
|
|
216
440
|
dialect,
|
|
@@ -232,31 +456,49 @@ function normalizeCreateTable(statement) {
|
|
|
232
456
|
reviewTodos: []
|
|
233
457
|
};
|
|
234
458
|
for (const definition of createDefinitions) {
|
|
235
|
-
if (definition.resource
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
459
|
+
if (definition.resource !== "column") continue;
|
|
460
|
+
const columnName = extractDeepColumnName2(definition.column);
|
|
461
|
+
const isPrimaryKey = hasPrimaryKey(definition);
|
|
462
|
+
const isNotNull = hasNotNull(definition);
|
|
463
|
+
const { type, size } = normalizeColumnType(definition.definition);
|
|
464
|
+
const check = definition.check;
|
|
465
|
+
const bounds = check?.definition ? extractCheckBounds(check.definition, columnName) : {};
|
|
466
|
+
const constraintNotes = extractColumnConstraintNotes(definition);
|
|
467
|
+
if (check?.definition) {
|
|
468
|
+
const expression = stringifyCheckDefinition(check.definition);
|
|
469
|
+
if (expression && (!bounds.minValue || !bounds.maxValue)) {
|
|
470
|
+
constraintNotes.push(`CHECK: ${expression}`);
|
|
471
|
+
}
|
|
248
472
|
}
|
|
249
|
-
|
|
250
|
-
|
|
473
|
+
table.columns.push({
|
|
474
|
+
name: columnName,
|
|
475
|
+
type,
|
|
476
|
+
size,
|
|
477
|
+
nullable: !isNotNull && !isPrimaryKey,
|
|
478
|
+
defaultValue: extractDefaultFromDef(definition),
|
|
479
|
+
minValue: bounds.minValue,
|
|
480
|
+
maxValue: bounds.maxValue,
|
|
481
|
+
isUnique: hasColumnUnique(definition),
|
|
482
|
+
isPrimaryKey,
|
|
483
|
+
isForeignKey: false,
|
|
484
|
+
comment: extractColumnComment(definition),
|
|
485
|
+
constraintNotes: constraintNotes.length > 0 ? constraintNotes : void 0
|
|
486
|
+
});
|
|
487
|
+
if (isPrimaryKey) table.primaryKeys.push(columnName);
|
|
488
|
+
}
|
|
489
|
+
for (const definition of createDefinitions) {
|
|
490
|
+
if (definition.resource !== "constraint") continue;
|
|
491
|
+
if (isConstraintType(definition.constraint_type, "PRIMARY KEY")) {
|
|
492
|
+
table.primaryKeys = extractDeepColumnNames2(definition.definition);
|
|
251
493
|
for (const column of table.columns) {
|
|
252
494
|
if (table.primaryKeys.includes(column.name)) column.isPrimaryKey = true;
|
|
253
495
|
}
|
|
254
496
|
}
|
|
255
|
-
if (
|
|
256
|
-
const columns =
|
|
497
|
+
if (isConstraintType(definition.constraint_type, "FOREIGN KEY")) {
|
|
498
|
+
const columns = extractDeepColumnNames2(definition.definition);
|
|
257
499
|
const refDef = definition.reference_definition;
|
|
258
500
|
const referencedTable = extractTableName(refDef?.table);
|
|
259
|
-
const referencedColumns =
|
|
501
|
+
const referencedColumns = extractDeepColumnNames2(refDef?.definition);
|
|
260
502
|
table.foreignKeys.push({
|
|
261
503
|
name: typeof definition.constraint === "string" ? definition.constraint : void 0,
|
|
262
504
|
columns,
|
|
@@ -267,14 +509,71 @@ function normalizeCreateTable(statement) {
|
|
|
267
509
|
if (columns.includes(column.name)) column.isForeignKey = true;
|
|
268
510
|
}
|
|
269
511
|
}
|
|
512
|
+
if (isConstraintType(definition.constraint_type, "UNIQUE")) {
|
|
513
|
+
const columns = extractConstraintColumnNames(definition.definition);
|
|
514
|
+
const label = typeof definition.constraint === "string" ? definition.constraint : "UNIQUE";
|
|
515
|
+
markColumnsUnique(table, columns, label);
|
|
516
|
+
}
|
|
517
|
+
if (isConstraintType(definition.constraint_type, "CHECK")) {
|
|
518
|
+
applyTableCheckConstraint(table, definition);
|
|
519
|
+
}
|
|
270
520
|
}
|
|
271
521
|
return table;
|
|
272
522
|
}
|
|
523
|
+
function markColumnsUnique(table, columns, label) {
|
|
524
|
+
const composite = columns.length > 1;
|
|
525
|
+
for (const columnName of columns) {
|
|
526
|
+
const column = table.columns.find((item) => item.name === columnName);
|
|
527
|
+
if (!column) continue;
|
|
528
|
+
column.isUnique = true;
|
|
529
|
+
if (composite) {
|
|
530
|
+
addConstraintNote(
|
|
531
|
+
column,
|
|
532
|
+
`UNIQUE (${label}: ${columns.join(", ")})`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function applyTableCheckConstraint(table, definition) {
|
|
538
|
+
const expression = stringifyCheckDefinition(definition.definition);
|
|
539
|
+
if (!expression) return;
|
|
540
|
+
const referencedColumns = /* @__PURE__ */ new Set();
|
|
541
|
+
for (const column of table.columns) {
|
|
542
|
+
const bounds = extractCheckBounds(definition.definition, column.name);
|
|
543
|
+
if (bounds.minValue) column.minValue = bounds.minValue;
|
|
544
|
+
if (bounds.maxValue) column.maxValue = bounds.maxValue;
|
|
545
|
+
if (bounds.minValue || bounds.maxValue) {
|
|
546
|
+
referencedColumns.add(column.name);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (expression.includes(column.name)) {
|
|
550
|
+
referencedColumns.add(column.name);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (referencedColumns.size === 0) {
|
|
554
|
+
for (const column of table.columns) {
|
|
555
|
+
addConstraintNote(column, `CHECK: ${expression}`);
|
|
556
|
+
}
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
for (const columnName of referencedColumns) {
|
|
560
|
+
const column = table.columns.find((item) => item.name === columnName);
|
|
561
|
+
if (!column) continue;
|
|
562
|
+
if (!column.minValue && !column.maxValue) {
|
|
563
|
+
addConstraintNote(column, `CHECK: ${expression}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function addConstraintNote(column, note) {
|
|
568
|
+
const notes = column.constraintNotes ?? [];
|
|
569
|
+
if (!notes.includes(note)) notes.push(note);
|
|
570
|
+
column.constraintNotes = notes;
|
|
571
|
+
}
|
|
273
572
|
function normalizeCreateIndex(statement) {
|
|
274
573
|
return {
|
|
275
574
|
name: String(statement.index ?? statement.index_name ?? "unnamed_index"),
|
|
276
575
|
table: extractTableName(statement.table),
|
|
277
|
-
columns:
|
|
576
|
+
columns: extractDeepColumnNames2(
|
|
278
577
|
statement.index_columns ?? statement.columns
|
|
279
578
|
),
|
|
280
579
|
unique: Boolean(statement.unique)
|
|
@@ -328,15 +627,15 @@ function extractTableName(value) {
|
|
|
328
627
|
}
|
|
329
628
|
return String(value ?? "unknown");
|
|
330
629
|
}
|
|
331
|
-
function
|
|
630
|
+
function extractDeepColumnName2(value) {
|
|
332
631
|
if (typeof value !== "object" || value === null)
|
|
333
632
|
return String(value ?? "unknown");
|
|
334
633
|
const object = value;
|
|
335
634
|
if (object.expr && typeof object.expr === "object") {
|
|
336
|
-
return
|
|
635
|
+
return extractDeepColumnName2(object.expr);
|
|
337
636
|
}
|
|
338
637
|
if (object.column && typeof object.column === "object") {
|
|
339
|
-
return
|
|
638
|
+
return extractDeepColumnName2(object.column);
|
|
340
639
|
}
|
|
341
640
|
if (object.value !== void 0) {
|
|
342
641
|
return String(object.value);
|
|
@@ -346,18 +645,9 @@ function extractDeepColumnName(value) {
|
|
|
346
645
|
}
|
|
347
646
|
return String(object.name ?? object.tableName ?? "unknown");
|
|
348
647
|
}
|
|
349
|
-
function
|
|
648
|
+
function extractDeepColumnNames2(value) {
|
|
350
649
|
if (!Array.isArray(value)) return [];
|
|
351
|
-
return value.map((item) =>
|
|
352
|
-
}
|
|
353
|
-
function normalizeType(value) {
|
|
354
|
-
if (typeof value === "object" && value !== null) {
|
|
355
|
-
const object = value;
|
|
356
|
-
return String(
|
|
357
|
-
object.dataType ?? object.type ?? object.name ?? "unknown"
|
|
358
|
-
).toLowerCase();
|
|
359
|
-
}
|
|
360
|
-
return String(value ?? "unknown").toLowerCase();
|
|
650
|
+
return value.map((item) => extractDeepColumnName2(item));
|
|
361
651
|
}
|
|
362
652
|
function hasPrimaryKey(def) {
|
|
363
653
|
if (def.primary_key) return true;
|
|
@@ -552,8 +842,8 @@ function dialectBias(dialect, requestedDialect, detectedDialect) {
|
|
|
552
842
|
}
|
|
553
843
|
|
|
554
844
|
// src/exporters/excel/excel-exporter.ts
|
|
555
|
-
var
|
|
556
|
-
var
|
|
845
|
+
var import_promises2 = require("fs/promises");
|
|
846
|
+
var import_node_path2 = require("path");
|
|
557
847
|
var import_exceljs = __toESM(require("exceljs"), 1);
|
|
558
848
|
|
|
559
849
|
// src/exporters/shared/output-labels.ts
|
|
@@ -576,6 +866,10 @@ var LABELS = {
|
|
|
576
866
|
type: "Type",
|
|
577
867
|
required: "Required",
|
|
578
868
|
defaultValue: "Default Value",
|
|
869
|
+
size: "Size",
|
|
870
|
+
minValue: "Min",
|
|
871
|
+
maxValue: "Max",
|
|
872
|
+
unique: "Unique",
|
|
579
873
|
notes: "Notes",
|
|
580
874
|
yes: "Yes",
|
|
581
875
|
no: "No",
|
|
@@ -604,7 +898,15 @@ var LABELS = {
|
|
|
604
898
|
rowNo: "#",
|
|
605
899
|
backToOverview: "\u2190 Overview",
|
|
606
900
|
pkMarker: "PK",
|
|
607
|
-
fkMarker: "FK"
|
|
901
|
+
fkMarker: "FK",
|
|
902
|
+
erDiagramHeading: "ER Diagram",
|
|
903
|
+
erDiagramSheet: "ER Diagram",
|
|
904
|
+
viewErDiagram: "View interactive ER diagram (html/er-diagram.html)",
|
|
905
|
+
zoomIn: "Zoom in",
|
|
906
|
+
zoomOut: "Zoom out",
|
|
907
|
+
zoomReset: "Reset",
|
|
908
|
+
zoomFit: "Fit",
|
|
909
|
+
panZoomHint: "Drag to pan \xB7 Scroll to zoom"
|
|
608
910
|
},
|
|
609
911
|
jp: {
|
|
610
912
|
docTitle: "Database Documentation",
|
|
@@ -624,6 +926,10 @@ var LABELS = {
|
|
|
624
926
|
type: "\u578B",
|
|
625
927
|
required: "\u5FC5\u9808",
|
|
626
928
|
defaultValue: "\u30C7\u30D5\u30A9\u30EB\u30C8\u5024",
|
|
929
|
+
size: "\u6841\u6570",
|
|
930
|
+
minValue: "\u6700\u5C0F\u5024",
|
|
931
|
+
maxValue: "\u6700\u5927\u5024",
|
|
932
|
+
unique: "\u4E00\u610F",
|
|
627
933
|
notes: "\u5099\u8003",
|
|
628
934
|
yes: "Yes",
|
|
629
935
|
no: "No",
|
|
@@ -652,13 +958,582 @@ var LABELS = {
|
|
|
652
958
|
rowNo: "No.",
|
|
653
959
|
backToOverview: "\u2190 \u4E00\u89A7",
|
|
654
960
|
pkMarker: "PK",
|
|
655
|
-
fkMarker: "FK"
|
|
961
|
+
fkMarker: "FK",
|
|
962
|
+
erDiagramHeading: "ER Diagram",
|
|
963
|
+
erDiagramSheet: "ER Diagram",
|
|
964
|
+
viewErDiagram: "\u30A4\u30F3\u30BF\u30E9\u30AF\u30C6\u30A3\u30D6ER\u56F3 (html/er-diagram.html)",
|
|
965
|
+
zoomIn: "\u62E1\u5927",
|
|
966
|
+
zoomOut: "\u7E2E\u5C0F",
|
|
967
|
+
zoomReset: "\u30EA\u30BB\u30C3\u30C8",
|
|
968
|
+
zoomFit: "\u5168\u4F53\u8868\u793A",
|
|
969
|
+
panZoomHint: "\u30C9\u30E9\u30C3\u30B0\u3067\u79FB\u52D5 \xB7 \u30B9\u30AF\u30ED\u30FC\u30EB\u3067\u62E1\u5927\u7E2E\u5C0F"
|
|
656
970
|
}
|
|
657
971
|
};
|
|
658
972
|
function getOutputLabels(language = "en") {
|
|
659
973
|
return LABELS[language];
|
|
660
974
|
}
|
|
661
975
|
|
|
976
|
+
// src/exporters/shared/column-definition.ts
|
|
977
|
+
var A5_COLUMN_COUNT = 10;
|
|
978
|
+
function columnDefinitionHeaders(labels) {
|
|
979
|
+
return [
|
|
980
|
+
labels.physicalName,
|
|
981
|
+
labels.logicalName,
|
|
982
|
+
labels.type,
|
|
983
|
+
labels.size,
|
|
984
|
+
labels.required,
|
|
985
|
+
labels.defaultValue,
|
|
986
|
+
labels.minValue,
|
|
987
|
+
labels.maxValue,
|
|
988
|
+
labels.unique,
|
|
989
|
+
labels.notes
|
|
990
|
+
];
|
|
991
|
+
}
|
|
992
|
+
function formatColumnNotes(column, labels) {
|
|
993
|
+
const parts = [];
|
|
994
|
+
if (column.isPrimaryKey) parts.push(labels.pkMarker);
|
|
995
|
+
if (column.isForeignKey) parts.push(labels.fkMarker);
|
|
996
|
+
if (column.constraintNotes?.length) parts.push(...column.constraintNotes);
|
|
997
|
+
if (column.description?.value) parts.push(column.description.value);
|
|
998
|
+
return parts.join(", ") || labels.none;
|
|
999
|
+
}
|
|
1000
|
+
function columnDefinitionRow(column, labels) {
|
|
1001
|
+
return [
|
|
1002
|
+
column.name,
|
|
1003
|
+
column.comment ?? "",
|
|
1004
|
+
column.type,
|
|
1005
|
+
column.size ?? labels.none,
|
|
1006
|
+
column.nullable ? labels.no : labels.yes,
|
|
1007
|
+
column.defaultValue ?? labels.none,
|
|
1008
|
+
column.minValue ?? labels.none,
|
|
1009
|
+
column.maxValue ?? labels.none,
|
|
1010
|
+
column.isUnique ? labels.yes : labels.no,
|
|
1011
|
+
formatColumnNotes(column, labels)
|
|
1012
|
+
];
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/exporters/diagram/mermaid-exporter.ts
|
|
1016
|
+
var import_promises = require("fs/promises");
|
|
1017
|
+
var import_node_path = require("path");
|
|
1018
|
+
async function exportMermaidDiagram(doc, options) {
|
|
1019
|
+
await (0, import_promises.mkdir)(options.outDir, { recursive: true });
|
|
1020
|
+
await (0, import_promises.writeFile)(
|
|
1021
|
+
(0, import_node_path.join)(options.outDir, "er_diagram.mmd"),
|
|
1022
|
+
renderMermaid(doc),
|
|
1023
|
+
"utf8"
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
function renderMermaid(doc) {
|
|
1027
|
+
const lines = ["erDiagram"];
|
|
1028
|
+
for (const warning of doc.warnings) {
|
|
1029
|
+
const target = warning.target ? ` (${warning.target})` : "";
|
|
1030
|
+
lines.push(
|
|
1031
|
+
` %% WARNING [${warning.severity}] ${warning.code}${target}: ${warning.message}`
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
for (const table of doc.tables) {
|
|
1035
|
+
for (const todo of table.reviewTodos) {
|
|
1036
|
+
lines.push(` %% TODO [${todo.type}] ${todo.target}: ${todo.issue}`);
|
|
1037
|
+
}
|
|
1038
|
+
lines.push(` ${table.name} {`);
|
|
1039
|
+
for (const column of table.columns) {
|
|
1040
|
+
const markers = [
|
|
1041
|
+
column.isPrimaryKey ? "PK" : "",
|
|
1042
|
+
column.isForeignKey ? "FK" : ""
|
|
1043
|
+
].filter(Boolean).join(" ");
|
|
1044
|
+
lines.push(
|
|
1045
|
+
` ${sanitizeType(column.type)} ${column.name}${markers ? ` "${markers}"` : ""}`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
lines.push(" }");
|
|
1049
|
+
}
|
|
1050
|
+
for (const relationship of doc.relationships.filter(
|
|
1051
|
+
(item) => item.source === "schema"
|
|
1052
|
+
)) {
|
|
1053
|
+
lines.push(
|
|
1054
|
+
` ${relationship.toTable} ||--o{ ${relationship.fromTable} : "${relationship.constraintName ?? relationship.fromColumn}"`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
return `${lines.join("\n")}
|
|
1058
|
+
`;
|
|
1059
|
+
}
|
|
1060
|
+
function sanitizeType(type) {
|
|
1061
|
+
return type.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/exporters/diagram/er-diagram-embed.ts
|
|
1065
|
+
function getErDiagramMermaid(doc) {
|
|
1066
|
+
return renderMermaid(doc);
|
|
1067
|
+
}
|
|
1068
|
+
function renderErDiagramHtmlPage(mermaidSource, labels) {
|
|
1069
|
+
const escaped = mermaidSource.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1070
|
+
return `<!DOCTYPE html>
|
|
1071
|
+
<html lang="en">
|
|
1072
|
+
<head>
|
|
1073
|
+
<meta charset="UTF-8">
|
|
1074
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1075
|
+
<title>${esc(labels.erDiagramHeading)}</title>
|
|
1076
|
+
<style>
|
|
1077
|
+
body { margin: 0; font-family: "Yu Gothic UI", "Meiryo", Arial, sans-serif; background: #f3f4f6; }
|
|
1078
|
+
.toolbar {
|
|
1079
|
+
background: #4472c4; color: #fff; padding: 10px 16px;
|
|
1080
|
+
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
|
1081
|
+
}
|
|
1082
|
+
.toolbar a { color: #fff; text-decoration: underline; }
|
|
1083
|
+
.toolbar .spacer { flex: 1; }
|
|
1084
|
+
.toolbar .hint { opacity: 0.9; font-size: 13px; }
|
|
1085
|
+
.toolbar button {
|
|
1086
|
+
background: #fff; color: #2f5597; border: none; border-radius: 4px;
|
|
1087
|
+
padding: 6px 12px; font-size: 13px; cursor: pointer; font-weight: 600;
|
|
1088
|
+
}
|
|
1089
|
+
.toolbar button:hover { background: #e8eef8; }
|
|
1090
|
+
.viewport {
|
|
1091
|
+
position: relative; height: calc(100vh - 52px); margin: 12px;
|
|
1092
|
+
background: #fff; border: 1px solid #bfc7d4; border-radius: 4px;
|
|
1093
|
+
overflow: hidden; cursor: grab; touch-action: none;
|
|
1094
|
+
}
|
|
1095
|
+
.viewport.dragging { cursor: grabbing; }
|
|
1096
|
+
.canvas {
|
|
1097
|
+
position: absolute; left: 0; top: 0; transform-origin: 0 0;
|
|
1098
|
+
padding: 24px;
|
|
1099
|
+
}
|
|
1100
|
+
.mermaid { min-width: 320px; }
|
|
1101
|
+
.mermaid svg { max-width: none !important; height: auto !important; }
|
|
1102
|
+
</style>
|
|
1103
|
+
</head>
|
|
1104
|
+
<body>
|
|
1105
|
+
<div class="toolbar">
|
|
1106
|
+
<strong>${esc(labels.erDiagramHeading)}</strong>
|
|
1107
|
+
<a href="index.html">\u2190 ${esc(labels.tableListHeading)}</a>
|
|
1108
|
+
<span class="spacer"></span>
|
|
1109
|
+
<span class="hint">${esc(labels.panZoomHint)}</span>
|
|
1110
|
+
<button type="button" id="zoom-out" title="${esc(labels.zoomOut)}">\u2212</button>
|
|
1111
|
+
<button type="button" id="zoom-reset" title="${esc(labels.zoomReset)}">${esc(labels.zoomReset)}</button>
|
|
1112
|
+
<button type="button" id="zoom-in" title="${esc(labels.zoomIn)}">+</button>
|
|
1113
|
+
<button type="button" id="zoom-fit" title="${esc(labels.zoomFit)}">${esc(labels.zoomFit)}</button>
|
|
1114
|
+
</div>
|
|
1115
|
+
<div class="viewport" id="viewport">
|
|
1116
|
+
<div class="canvas" id="canvas">
|
|
1117
|
+
<pre class="mermaid">${escaped}</pre>
|
|
1118
|
+
</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
<script type="module">
|
|
1121
|
+
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
1122
|
+
|
|
1123
|
+
mermaid.initialize({
|
|
1124
|
+
startOnLoad: false,
|
|
1125
|
+
theme: "default",
|
|
1126
|
+
er: { useMaxWidth: false }
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
await mermaid.run({ querySelector: ".mermaid" });
|
|
1130
|
+
setupPanZoom(
|
|
1131
|
+
document.getElementById("viewport"),
|
|
1132
|
+
document.getElementById("canvas")
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
function setupPanZoom(viewport, canvas) {
|
|
1136
|
+
let scale = 1;
|
|
1137
|
+
let tx = 40;
|
|
1138
|
+
let ty = 40;
|
|
1139
|
+
let dragging = false;
|
|
1140
|
+
let lastX = 0;
|
|
1141
|
+
let lastY = 0;
|
|
1142
|
+
|
|
1143
|
+
function apply() {
|
|
1144
|
+
canvas.style.transform = "translate(" + tx + "px," + ty + "px) scale(" + scale + ")";
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function zoomAt(factor, cx, cy) {
|
|
1148
|
+
const next = Math.min(4, Math.max(0.15, scale * factor));
|
|
1149
|
+
const ratio = next / scale;
|
|
1150
|
+
tx = cx - (cx - tx) * ratio;
|
|
1151
|
+
ty = cy - (cy - ty) * ratio;
|
|
1152
|
+
scale = next;
|
|
1153
|
+
apply();
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function fitToView() {
|
|
1157
|
+
const svg = canvas.querySelector("svg");
|
|
1158
|
+
if (!svg) return;
|
|
1159
|
+
const box = svg.getBBox();
|
|
1160
|
+
const pad = 32;
|
|
1161
|
+
const vw = viewport.clientWidth;
|
|
1162
|
+
const vh = viewport.clientHeight;
|
|
1163
|
+
scale = Math.min(
|
|
1164
|
+
(vw - pad * 2) / Math.max(box.width, 1),
|
|
1165
|
+
(vh - pad * 2) / Math.max(box.height, 1),
|
|
1166
|
+
1.5
|
|
1167
|
+
);
|
|
1168
|
+
tx = (vw - box.width * scale) / 2 - box.x * scale;
|
|
1169
|
+
ty = (vh - box.height * scale) / 2 - box.y * scale;
|
|
1170
|
+
apply();
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
viewport.addEventListener("wheel", (e) => {
|
|
1174
|
+
e.preventDefault();
|
|
1175
|
+
const rect = viewport.getBoundingClientRect();
|
|
1176
|
+
const cx = e.clientX - rect.left;
|
|
1177
|
+
const cy = e.clientY - rect.top;
|
|
1178
|
+
zoomAt(e.deltaY < 0 ? 1.12 : 0.89, cx, cy);
|
|
1179
|
+
}, { passive: false });
|
|
1180
|
+
|
|
1181
|
+
viewport.addEventListener("mousedown", (e) => {
|
|
1182
|
+
if (e.button !== 0) return;
|
|
1183
|
+
dragging = true;
|
|
1184
|
+
lastX = e.clientX;
|
|
1185
|
+
lastY = e.clientY;
|
|
1186
|
+
viewport.classList.add("dragging");
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
window.addEventListener("mousemove", (e) => {
|
|
1190
|
+
if (!dragging) return;
|
|
1191
|
+
tx += e.clientX - lastX;
|
|
1192
|
+
ty += e.clientY - lastY;
|
|
1193
|
+
lastX = e.clientX;
|
|
1194
|
+
lastY = e.clientY;
|
|
1195
|
+
apply();
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
window.addEventListener("mouseup", () => {
|
|
1199
|
+
dragging = false;
|
|
1200
|
+
viewport.classList.remove("dragging");
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
document.getElementById("zoom-in").addEventListener("click", () => {
|
|
1204
|
+
zoomAt(1.2, viewport.clientWidth / 2, viewport.clientHeight / 2);
|
|
1205
|
+
});
|
|
1206
|
+
document.getElementById("zoom-out").addEventListener("click", () => {
|
|
1207
|
+
zoomAt(1 / 1.2, viewport.clientWidth / 2, viewport.clientHeight / 2);
|
|
1208
|
+
});
|
|
1209
|
+
document.getElementById("zoom-reset").addEventListener("click", () => {
|
|
1210
|
+
scale = 1;
|
|
1211
|
+
tx = 40;
|
|
1212
|
+
ty = 40;
|
|
1213
|
+
apply();
|
|
1214
|
+
});
|
|
1215
|
+
document.getElementById("zoom-fit").addEventListener("click", fitToView);
|
|
1216
|
+
|
|
1217
|
+
fitToView();
|
|
1218
|
+
}
|
|
1219
|
+
</script>
|
|
1220
|
+
</body>
|
|
1221
|
+
</html>`;
|
|
1222
|
+
}
|
|
1223
|
+
function renderErDiagramMarkdown(mermaidSource, labels) {
|
|
1224
|
+
return [
|
|
1225
|
+
`# ${labels.erDiagramHeading}`,
|
|
1226
|
+
"",
|
|
1227
|
+
"```mermaid",
|
|
1228
|
+
mermaidSource.trimEnd(),
|
|
1229
|
+
"```",
|
|
1230
|
+
""
|
|
1231
|
+
].join("\n");
|
|
1232
|
+
}
|
|
1233
|
+
function esc(text) {
|
|
1234
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/exporters/diagram/er-diagram-layout.ts
|
|
1238
|
+
var import_elk_bundled = __toESM(require("elkjs/lib/elk.bundled.js"), 1);
|
|
1239
|
+
var BOX_W = 200;
|
|
1240
|
+
var HEADER_H = 28;
|
|
1241
|
+
var LINE_H = 14;
|
|
1242
|
+
var MAX_COLS_SHOWN = 6;
|
|
1243
|
+
var COMPACT_THRESHOLD = 18;
|
|
1244
|
+
var PAD = 24;
|
|
1245
|
+
var CLUSTER_GAP = 56;
|
|
1246
|
+
function isCompactLayout(tableCount) {
|
|
1247
|
+
return tableCount >= COMPACT_THRESHOLD;
|
|
1248
|
+
}
|
|
1249
|
+
function getVisibleErColumns(table) {
|
|
1250
|
+
const prioritized = [
|
|
1251
|
+
...table.columns.filter((column) => column.isPrimaryKey),
|
|
1252
|
+
...table.columns.filter(
|
|
1253
|
+
(column) => column.isForeignKey && !column.isPrimaryKey
|
|
1254
|
+
),
|
|
1255
|
+
...table.columns.filter(
|
|
1256
|
+
(column) => !column.isPrimaryKey && !column.isForeignKey
|
|
1257
|
+
)
|
|
1258
|
+
];
|
|
1259
|
+
const unique = prioritized.filter(
|
|
1260
|
+
(column, index, columns) => columns.findIndex((item) => item.name === column.name) === index
|
|
1261
|
+
);
|
|
1262
|
+
return unique.slice(0, MAX_COLS_SHOWN);
|
|
1263
|
+
}
|
|
1264
|
+
function measureTableBox(table, _compact = false) {
|
|
1265
|
+
const visible = getVisibleErColumns(table);
|
|
1266
|
+
const extra = table.columns.length > visible.length ? 1 : 0;
|
|
1267
|
+
return {
|
|
1268
|
+
w: BOX_W,
|
|
1269
|
+
h: HEADER_H + (visible.length + extra) * LINE_H + 8
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function buildAdjacency(doc) {
|
|
1273
|
+
const names = new Set(doc.tables.map((t) => t.name));
|
|
1274
|
+
const adj = /* @__PURE__ */ new Map();
|
|
1275
|
+
for (const name of names) adj.set(name, /* @__PURE__ */ new Set());
|
|
1276
|
+
for (const rel of doc.relationships.filter((r) => r.source === "schema")) {
|
|
1277
|
+
if (!names.has(rel.fromTable) || !names.has(rel.toTable)) continue;
|
|
1278
|
+
adj.get(rel.fromTable).add(rel.toTable);
|
|
1279
|
+
adj.get(rel.toTable).add(rel.fromTable);
|
|
1280
|
+
}
|
|
1281
|
+
return adj;
|
|
1282
|
+
}
|
|
1283
|
+
function connectedComponents(tableNames, adj) {
|
|
1284
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1285
|
+
const components = [];
|
|
1286
|
+
for (const name of tableNames) {
|
|
1287
|
+
if (visited.has(name)) continue;
|
|
1288
|
+
const stack = [name];
|
|
1289
|
+
const component = [];
|
|
1290
|
+
visited.add(name);
|
|
1291
|
+
while (stack.length > 0) {
|
|
1292
|
+
const current = stack.pop();
|
|
1293
|
+
component.push(current);
|
|
1294
|
+
for (const next of adj.get(current) ?? []) {
|
|
1295
|
+
if (!visited.has(next)) {
|
|
1296
|
+
visited.add(next);
|
|
1297
|
+
stack.push(next);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
component.sort();
|
|
1302
|
+
components.push(component);
|
|
1303
|
+
}
|
|
1304
|
+
return components.sort((a, b) => b.length - a.length);
|
|
1305
|
+
}
|
|
1306
|
+
function sectionToPoints(section) {
|
|
1307
|
+
return [section.startPoint, ...section.bendPoints ?? [], section.endPoint];
|
|
1308
|
+
}
|
|
1309
|
+
function extractEdges(layouted) {
|
|
1310
|
+
const edges = [];
|
|
1311
|
+
for (const edge of layouted.edges ?? []) {
|
|
1312
|
+
for (const section of edge.sections ?? []) {
|
|
1313
|
+
edges.push({
|
|
1314
|
+
id: edge.id,
|
|
1315
|
+
points: sectionToPoints(section)
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return edges;
|
|
1320
|
+
}
|
|
1321
|
+
async function layoutComponent(doc, tableNames, compact) {
|
|
1322
|
+
const elk = new import_elk_bundled.default();
|
|
1323
|
+
const nameSet = new Set(tableNames);
|
|
1324
|
+
const tables = doc.tables.filter((t) => nameSet.has(t.name));
|
|
1325
|
+
const direction = tables.length >= 8 ? "DOWN" : "RIGHT";
|
|
1326
|
+
const children = tables.map((table) => {
|
|
1327
|
+
const { w, h } = measureTableBox(table, compact);
|
|
1328
|
+
return { id: table.name, width: w, height: h };
|
|
1329
|
+
});
|
|
1330
|
+
const edges = [];
|
|
1331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1332
|
+
for (const rel of doc.relationships.filter((r) => r.source === "schema")) {
|
|
1333
|
+
if (!nameSet.has(rel.fromTable) || !nameSet.has(rel.toTable)) continue;
|
|
1334
|
+
const key = `${rel.fromTable}->${rel.toTable}`;
|
|
1335
|
+
if (seen.has(key)) continue;
|
|
1336
|
+
seen.add(key);
|
|
1337
|
+
edges.push({
|
|
1338
|
+
id: key,
|
|
1339
|
+
sources: [rel.fromTable],
|
|
1340
|
+
targets: [rel.toTable]
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
const graph = {
|
|
1344
|
+
id: "root",
|
|
1345
|
+
layoutOptions: {
|
|
1346
|
+
"elk.algorithm": "layered",
|
|
1347
|
+
"elk.direction": direction,
|
|
1348
|
+
"elk.edgeRouting": "ORTHOGONAL",
|
|
1349
|
+
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
|
1350
|
+
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
|
|
1351
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": compact ? "56" : "80",
|
|
1352
|
+
"elk.spacing.nodeNode": compact ? "32" : "48",
|
|
1353
|
+
"elk.spacing.edgeNode": "24",
|
|
1354
|
+
"elk.padding": `[top=${PAD},left=${PAD},bottom=${PAD},right=${PAD}]`
|
|
1355
|
+
},
|
|
1356
|
+
children,
|
|
1357
|
+
edges
|
|
1358
|
+
};
|
|
1359
|
+
const layouted = await elk.layout(graph);
|
|
1360
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
1361
|
+
let minX = Infinity;
|
|
1362
|
+
let minY = Infinity;
|
|
1363
|
+
let maxX = -Infinity;
|
|
1364
|
+
let maxY = -Infinity;
|
|
1365
|
+
for (const child of layouted.children ?? []) {
|
|
1366
|
+
const table = tables.find((t) => t.name === child.id);
|
|
1367
|
+
if (!table) continue;
|
|
1368
|
+
const { w, h } = measureTableBox(table, compact);
|
|
1369
|
+
const x = child.x ?? 0;
|
|
1370
|
+
const y = child.y ?? 0;
|
|
1371
|
+
const box = { x, y, w, h };
|
|
1372
|
+
boxes.set(child.id, box);
|
|
1373
|
+
minX = Math.min(minX, x);
|
|
1374
|
+
minY = Math.min(minY, y);
|
|
1375
|
+
maxX = Math.max(maxX, x + w);
|
|
1376
|
+
maxY = Math.max(maxY, y + h);
|
|
1377
|
+
}
|
|
1378
|
+
const width = Math.ceil(maxX - minX + PAD * 2);
|
|
1379
|
+
const height = Math.ceil(maxY - minY + PAD * 2);
|
|
1380
|
+
const dx = PAD - minX;
|
|
1381
|
+
const dy = PAD - minY;
|
|
1382
|
+
for (const [name, box] of boxes) {
|
|
1383
|
+
boxes.set(name, { x: box.x + dx, y: box.y + dy, w: box.w, h: box.h });
|
|
1384
|
+
}
|
|
1385
|
+
const shiftedEdges = extractEdges(layouted).map((edge) => ({
|
|
1386
|
+
...edge,
|
|
1387
|
+
points: edge.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
|
|
1388
|
+
}));
|
|
1389
|
+
return { boxes, edges: shiftedEdges, width, height };
|
|
1390
|
+
}
|
|
1391
|
+
function shiftLayout(boxes, edges, offsetX, offsetY) {
|
|
1392
|
+
for (const [name, box] of boxes) {
|
|
1393
|
+
boxes.set(name, { ...box, x: box.x + offsetX, y: box.y + offsetY });
|
|
1394
|
+
}
|
|
1395
|
+
for (const edge of edges) {
|
|
1396
|
+
for (const p of edge.points) {
|
|
1397
|
+
p.x += offsetX;
|
|
1398
|
+
p.y += offsetY;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async function layoutErDiagram(doc) {
|
|
1403
|
+
const tables = doc.tables;
|
|
1404
|
+
if (tables.length === 0) {
|
|
1405
|
+
return {
|
|
1406
|
+
boxes: /* @__PURE__ */ new Map(),
|
|
1407
|
+
edges: [],
|
|
1408
|
+
compact: false,
|
|
1409
|
+
width: 400,
|
|
1410
|
+
height: 80
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
const compact = isCompactLayout(tables.length);
|
|
1414
|
+
const adj = buildAdjacency(doc);
|
|
1415
|
+
const components = connectedComponents(
|
|
1416
|
+
tables.map((t) => t.name),
|
|
1417
|
+
adj
|
|
1418
|
+
);
|
|
1419
|
+
const mergedBoxes = /* @__PURE__ */ new Map();
|
|
1420
|
+
const mergedEdges = [];
|
|
1421
|
+
const clusterCols = components.length <= 1 ? 1 : components.length <= 4 ? 2 : 3;
|
|
1422
|
+
let tileX = 0;
|
|
1423
|
+
let tileY = 0;
|
|
1424
|
+
let rowHeight = 0;
|
|
1425
|
+
let maxWidth = PAD;
|
|
1426
|
+
let maxHeight = PAD;
|
|
1427
|
+
for (const [i, component] of components.entries()) {
|
|
1428
|
+
const laid = await layoutComponent(doc, component, compact);
|
|
1429
|
+
shiftLayout(laid.boxes, laid.edges, tileX, tileY);
|
|
1430
|
+
for (const [name, box] of laid.boxes) mergedBoxes.set(name, box);
|
|
1431
|
+
mergedEdges.push(...laid.edges);
|
|
1432
|
+
rowHeight = Math.max(rowHeight, laid.height);
|
|
1433
|
+
tileX += laid.width + CLUSTER_GAP;
|
|
1434
|
+
maxWidth = Math.max(maxWidth, tileX);
|
|
1435
|
+
maxHeight = Math.max(maxHeight, tileY + laid.height);
|
|
1436
|
+
if ((i + 1) % clusterCols === 0) {
|
|
1437
|
+
tileX = 0;
|
|
1438
|
+
tileY += rowHeight + CLUSTER_GAP;
|
|
1439
|
+
rowHeight = 0;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return {
|
|
1443
|
+
boxes: mergedBoxes,
|
|
1444
|
+
edges: mergedEdges,
|
|
1445
|
+
compact,
|
|
1446
|
+
width: Math.ceil(maxWidth),
|
|
1447
|
+
height: Math.ceil(maxHeight + PAD)
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// src/exporters/diagram/er-diagram-svg.ts
|
|
1452
|
+
async function renderErDiagramSvg(doc) {
|
|
1453
|
+
const tables = doc.tables;
|
|
1454
|
+
if (tables.length === 0) {
|
|
1455
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="80"><text x="10" y="40" font-family="Arial,sans-serif" font-size="14">No tables</text></svg>`;
|
|
1456
|
+
}
|
|
1457
|
+
const layout = await layoutErDiagram(doc);
|
|
1458
|
+
return buildErDiagramSvg(doc, layout);
|
|
1459
|
+
}
|
|
1460
|
+
async function renderErDiagramPng(doc) {
|
|
1461
|
+
const tables = doc.tables;
|
|
1462
|
+
if (tables.length === 0) {
|
|
1463
|
+
const svg2 = await renderErDiagramSvg(doc);
|
|
1464
|
+
const sharp2 = (await import("sharp")).default;
|
|
1465
|
+
const buffer2 = await sharp2(Buffer.from(svg2)).png().toBuffer();
|
|
1466
|
+
return { buffer: buffer2, width: 400, height: 80 };
|
|
1467
|
+
}
|
|
1468
|
+
const layout = await layoutErDiagram(doc);
|
|
1469
|
+
const svg = buildErDiagramSvg(doc, layout);
|
|
1470
|
+
const sharp = (await import("sharp")).default;
|
|
1471
|
+
const buffer = await sharp(Buffer.from(svg)).png().toBuffer();
|
|
1472
|
+
return { buffer, width: layout.width, height: layout.height };
|
|
1473
|
+
}
|
|
1474
|
+
function buildErDiagramSvg(doc, layout) {
|
|
1475
|
+
const { boxes, edges, width, height } = layout;
|
|
1476
|
+
const parts = [
|
|
1477
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" font-family="Arial,sans-serif" font-size="11">`,
|
|
1478
|
+
`<rect width="100%" height="100%" fill="#ffffff"/>`,
|
|
1479
|
+
`<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#5b7aa6"/></marker></defs>`,
|
|
1480
|
+
`<g class="edges">`
|
|
1481
|
+
];
|
|
1482
|
+
for (const edge of edges) {
|
|
1483
|
+
if (edge.points.length < 2) continue;
|
|
1484
|
+
const d = pointsToPath(edge.points);
|
|
1485
|
+
parts.push(
|
|
1486
|
+
`<path d="${d}" fill="none" stroke="#7d96b8" stroke-width="1.25" marker-end="url(#arrow)"/>`
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
parts.push(`</g><g class="nodes">`);
|
|
1490
|
+
for (const table of doc.tables) {
|
|
1491
|
+
const box = boxes.get(table.name);
|
|
1492
|
+
parts.push(...renderTableBox(table, box));
|
|
1493
|
+
}
|
|
1494
|
+
parts.push("</g></svg>");
|
|
1495
|
+
return parts.join("");
|
|
1496
|
+
}
|
|
1497
|
+
function pointsToPath(points) {
|
|
1498
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(" ");
|
|
1499
|
+
}
|
|
1500
|
+
function renderTableBox(table, box) {
|
|
1501
|
+
const parts = [
|
|
1502
|
+
`<rect x="${box.x}" y="${box.y}" width="${box.w}" height="${box.h}" fill="#f8fafc" stroke="#4472c4" stroke-width="1.5" rx="4"/>`,
|
|
1503
|
+
`<rect x="${box.x}" y="${box.y}" width="${box.w}" height="${HEADER_H}" fill="#4472c4" rx="4"/>`,
|
|
1504
|
+
`<rect x="${box.x}" y="${box.y + HEADER_H - 4}" width="${box.w}" height="4" fill="#4472c4"/>`,
|
|
1505
|
+
`<text x="${box.x + 8}" y="${box.y + 18}" fill="#ffffff" font-weight="bold">${escapeXml(table.name)}</text>`
|
|
1506
|
+
];
|
|
1507
|
+
let cy = box.y + HEADER_H + 14;
|
|
1508
|
+
const visible = getVisibleErColumns(table);
|
|
1509
|
+
for (const col of visible) {
|
|
1510
|
+
const marker = col.isPrimaryKey ? " PK" : col.isForeignKey ? " FK" : "";
|
|
1511
|
+
parts.push(
|
|
1512
|
+
`<text x="${box.x + 8}" y="${cy}" fill="#333333">${escapeXml(col.name)} : ${escapeXml(shortType(col.type))}${marker}</text>`
|
|
1513
|
+
);
|
|
1514
|
+
cy += LINE_H;
|
|
1515
|
+
}
|
|
1516
|
+
if (table.columns.length > visible.length) {
|
|
1517
|
+
parts.push(
|
|
1518
|
+
`<text x="${box.x + 8}" y="${cy}" fill="#666666">... +${table.columns.length - visible.length} more</text>`
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
return parts;
|
|
1522
|
+
}
|
|
1523
|
+
function shortType(type) {
|
|
1524
|
+
return type.length > 18 ? `${type.slice(0, 15)}...` : type;
|
|
1525
|
+
}
|
|
1526
|
+
function escapeXml(text) {
|
|
1527
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1528
|
+
}
|
|
1529
|
+
function fitErDiagramToBox(width, height, maxWidth, maxHeight) {
|
|
1530
|
+
const scale = Math.min(maxWidth / width, maxHeight / height, 1);
|
|
1531
|
+
return {
|
|
1532
|
+
width: Math.max(1, Math.round(width * scale)),
|
|
1533
|
+
height: Math.max(1, Math.round(height * scale))
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
|
|
662
1537
|
// src/exporters/excel/excel-exporter.ts
|
|
663
1538
|
var COLOR = {
|
|
664
1539
|
headerBg: "FF4472C4",
|
|
@@ -676,7 +1551,7 @@ var COLOR = {
|
|
|
676
1551
|
};
|
|
677
1552
|
var COL_COUNT = 7;
|
|
678
1553
|
async function exportExcelDictionary(doc, options) {
|
|
679
|
-
await (0,
|
|
1554
|
+
await (0, import_promises2.mkdir)(options.outDir, { recursive: true });
|
|
680
1555
|
const workbook = new import_exceljs.default.Workbook();
|
|
681
1556
|
const labels = getOutputLabels(options.language);
|
|
682
1557
|
const sheetNames = /* @__PURE__ */ new Map();
|
|
@@ -684,13 +1559,16 @@ async function exportExcelDictionary(doc, options) {
|
|
|
684
1559
|
sheetNames.set(table.name, buildSheetName(table.name, sheetNames));
|
|
685
1560
|
}
|
|
686
1561
|
addOverviewSheet(workbook, doc, labels, sheetNames);
|
|
1562
|
+
if (doc.tables.length > 0) {
|
|
1563
|
+
await addErDiagramSheet(workbook, doc, labels, options.outDir);
|
|
1564
|
+
}
|
|
687
1565
|
for (const table of doc.tables) {
|
|
688
1566
|
const sheetName = sheetNames.get(table.name);
|
|
689
1567
|
const sheet = workbook.addWorksheet(sheetName);
|
|
690
1568
|
populateTableSheet(sheet, table, doc, labels);
|
|
691
1569
|
}
|
|
692
1570
|
await workbook.xlsx.writeFile(
|
|
693
|
-
(0,
|
|
1571
|
+
(0, import_node_path2.join)(options.outDir, "database_dictionary.xlsx")
|
|
694
1572
|
);
|
|
695
1573
|
}
|
|
696
1574
|
function addOverviewSheet(workbook, doc, labels, sheetNames) {
|
|
@@ -771,6 +1649,51 @@ function addOverviewSheet(workbook, doc, labels, sheetNames) {
|
|
|
771
1649
|
};
|
|
772
1650
|
sheet.views = [{ state: "frozen", ySplit: headerRowNum }];
|
|
773
1651
|
}
|
|
1652
|
+
async function addErDiagramSheet(workbook, doc, labels, outDir) {
|
|
1653
|
+
const sheet = workbook.addWorksheet(labels.erDiagramSheet);
|
|
1654
|
+
sheet.mergeCells(1, 1, 1, 6);
|
|
1655
|
+
const titleCell = sheet.getCell(1, 1);
|
|
1656
|
+
titleCell.value = labels.erDiagramHeading;
|
|
1657
|
+
titleCell.font = { bold: true, size: 14, color: { argb: COLOR.overviewFg } };
|
|
1658
|
+
titleCell.fill = solidFill(COLOR.overviewBg);
|
|
1659
|
+
titleCell.alignment = { horizontal: "center", vertical: "middle" };
|
|
1660
|
+
sheet.getRow(1).height = 28;
|
|
1661
|
+
let nextRow = 3;
|
|
1662
|
+
try {
|
|
1663
|
+
const { buffer: png, width, height } = await renderErDiagramPng(doc);
|
|
1664
|
+
await (0, import_promises2.writeFile)((0, import_node_path2.join)(outDir, "er_diagram.png"), png);
|
|
1665
|
+
const imageId = workbook.addImage({
|
|
1666
|
+
base64: png.toString("base64"),
|
|
1667
|
+
extension: "png"
|
|
1668
|
+
});
|
|
1669
|
+
const fitted = fitErDiagramToBox(width, height, 1100, 1200);
|
|
1670
|
+
sheet.addImage(imageId, {
|
|
1671
|
+
tl: { col: 0, row: 2 },
|
|
1672
|
+
ext: fitted
|
|
1673
|
+
});
|
|
1674
|
+
nextRow = Math.max(28, Math.ceil(fitted.height / 18) + 4);
|
|
1675
|
+
} catch {
|
|
1676
|
+
sheet.getCell(3, 1).value = labels.viewErDiagram;
|
|
1677
|
+
nextRow = 5;
|
|
1678
|
+
}
|
|
1679
|
+
const mermaid = getErDiagramMermaid(doc);
|
|
1680
|
+
sheet.getCell(nextRow, 1).value = "Mermaid source";
|
|
1681
|
+
sheet.getCell(nextRow, 1).font = { bold: true, color: { argb: COLOR.metaFg } };
|
|
1682
|
+
nextRow += 1;
|
|
1683
|
+
sheet.mergeCells(nextRow, 1, nextRow + 20, 6);
|
|
1684
|
+
const sourceCell = sheet.getCell(nextRow, 1);
|
|
1685
|
+
sourceCell.value = mermaid;
|
|
1686
|
+
sourceCell.alignment = { wrapText: true, vertical: "top" };
|
|
1687
|
+
sourceCell.font = { name: "Courier New", size: 9 };
|
|
1688
|
+
sheet.columns = [
|
|
1689
|
+
{ width: 24 },
|
|
1690
|
+
{ width: 24 },
|
|
1691
|
+
{ width: 24 },
|
|
1692
|
+
{ width: 24 },
|
|
1693
|
+
{ width: 24 },
|
|
1694
|
+
{ width: 24 }
|
|
1695
|
+
];
|
|
1696
|
+
}
|
|
774
1697
|
function populateTableSheet(sheet, table, doc, labels) {
|
|
775
1698
|
const indexes = collectTableIndexes(table, doc);
|
|
776
1699
|
sheet.mergeCells(1, 1, 1, 6);
|
|
@@ -811,48 +1734,39 @@ function populateTableSheet(sheet, table, doc, labels) {
|
|
|
811
1734
|
row.getCell(2).alignment = { wrapText: true, vertical: "top" };
|
|
812
1735
|
}
|
|
813
1736
|
sheet.addRow([]);
|
|
814
|
-
const headerRow = sheet.addRow(
|
|
815
|
-
labels.physicalName,
|
|
816
|
-
labels.logicalName,
|
|
817
|
-
labels.type,
|
|
818
|
-
labels.required,
|
|
819
|
-
labels.defaultValue,
|
|
820
|
-
labels.notes
|
|
821
|
-
]);
|
|
1737
|
+
const headerRow = sheet.addRow(columnDefinitionHeaders(labels));
|
|
822
1738
|
styleColorRow(headerRow, COLOR.headerBg, COLOR.headerFg);
|
|
823
|
-
applyBorderToRow(headerRow,
|
|
1739
|
+
applyBorderToRow(headerRow, A5_COLUMN_COUNT);
|
|
824
1740
|
const headerRowNum = headerRow.number;
|
|
825
1741
|
for (const [i, column] of table.columns.entries()) {
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
column.name,
|
|
832
|
-
displayValue(column.comment, labels),
|
|
833
|
-
column.type,
|
|
834
|
-
column.nullable ? labels.no : labels.yes,
|
|
835
|
-
column.defaultValue ?? "-",
|
|
836
|
-
notes || "-"
|
|
837
|
-
]);
|
|
1742
|
+
const row = sheet.addRow(
|
|
1743
|
+
columnDefinitionRow(column, labels).map(
|
|
1744
|
+
(value, index) => index === 1 ? displayValue(value, labels) : value
|
|
1745
|
+
)
|
|
1746
|
+
);
|
|
838
1747
|
if (column.isPrimaryKey) {
|
|
839
|
-
shadeRow(row,
|
|
1748
|
+
shadeRow(row, A5_COLUMN_COUNT, COLOR.pkBg);
|
|
840
1749
|
row.getCell(1).font = { bold: true };
|
|
841
1750
|
} else if (column.isForeignKey) {
|
|
842
|
-
shadeRow(row,
|
|
1751
|
+
shadeRow(row, A5_COLUMN_COUNT, COLOR.fkBg);
|
|
843
1752
|
} else if (i % 2 === 1) {
|
|
844
|
-
shadeRow(row,
|
|
1753
|
+
shadeRow(row, A5_COLUMN_COUNT, COLOR.altRow);
|
|
845
1754
|
}
|
|
846
|
-
row.getCell(
|
|
847
|
-
|
|
1755
|
+
row.getCell(5).alignment = { horizontal: "center" };
|
|
1756
|
+
row.getCell(9).alignment = { horizontal: "center" };
|
|
1757
|
+
applyBorderToRow(row, A5_COLUMN_COUNT);
|
|
848
1758
|
}
|
|
849
1759
|
sheet.columns = [
|
|
1760
|
+
{ width: 22 },
|
|
850
1761
|
{ width: 24 },
|
|
851
|
-
{ width:
|
|
852
|
-
{ width: 18 },
|
|
1762
|
+
{ width: 16 },
|
|
853
1763
|
{ width: 10 },
|
|
854
|
-
{ width:
|
|
855
|
-
{ width:
|
|
1764
|
+
{ width: 8 },
|
|
1765
|
+
{ width: 14 },
|
|
1766
|
+
{ width: 8 },
|
|
1767
|
+
{ width: 8 },
|
|
1768
|
+
{ width: 8 },
|
|
1769
|
+
{ width: 28 }
|
|
856
1770
|
];
|
|
857
1771
|
sheet.views = [{ state: "frozen", ySplit: headerRowNum }];
|
|
858
1772
|
}
|
|
@@ -922,55 +1836,6 @@ function collectTableIndexes(table, doc) {
|
|
|
922
1836
|
];
|
|
923
1837
|
}
|
|
924
1838
|
|
|
925
|
-
// src/exporters/diagram/mermaid-exporter.ts
|
|
926
|
-
var import_promises2 = require("fs/promises");
|
|
927
|
-
var import_node_path2 = require("path");
|
|
928
|
-
async function exportMermaidDiagram(doc, options) {
|
|
929
|
-
await (0, import_promises2.mkdir)(options.outDir, { recursive: true });
|
|
930
|
-
await (0, import_promises2.writeFile)(
|
|
931
|
-
(0, import_node_path2.join)(options.outDir, "er_diagram.mmd"),
|
|
932
|
-
renderMermaid(doc),
|
|
933
|
-
"utf8"
|
|
934
|
-
);
|
|
935
|
-
}
|
|
936
|
-
function renderMermaid(doc) {
|
|
937
|
-
const lines = ["erDiagram"];
|
|
938
|
-
for (const warning of doc.warnings) {
|
|
939
|
-
const target = warning.target ? ` (${warning.target})` : "";
|
|
940
|
-
lines.push(
|
|
941
|
-
` %% WARNING [${warning.severity}] ${warning.code}${target}: ${warning.message}`
|
|
942
|
-
);
|
|
943
|
-
}
|
|
944
|
-
for (const table of doc.tables) {
|
|
945
|
-
for (const todo of table.reviewTodos) {
|
|
946
|
-
lines.push(` %% TODO [${todo.type}] ${todo.target}: ${todo.issue}`);
|
|
947
|
-
}
|
|
948
|
-
lines.push(` ${table.name} {`);
|
|
949
|
-
for (const column of table.columns) {
|
|
950
|
-
const markers = [
|
|
951
|
-
column.isPrimaryKey ? "PK" : "",
|
|
952
|
-
column.isForeignKey ? "FK" : ""
|
|
953
|
-
].filter(Boolean).join(" ");
|
|
954
|
-
lines.push(
|
|
955
|
-
` ${sanitizeType(column.type)} ${column.name}${markers ? ` "${markers}"` : ""}`
|
|
956
|
-
);
|
|
957
|
-
}
|
|
958
|
-
lines.push(" }");
|
|
959
|
-
}
|
|
960
|
-
for (const relationship of doc.relationships.filter(
|
|
961
|
-
(item) => item.source === "schema"
|
|
962
|
-
)) {
|
|
963
|
-
lines.push(
|
|
964
|
-
` ${relationship.toTable} ||--o{ ${relationship.fromTable} : "${relationship.constraintName ?? relationship.fromColumn}"`
|
|
965
|
-
);
|
|
966
|
-
}
|
|
967
|
-
return `${lines.join("\n")}
|
|
968
|
-
`;
|
|
969
|
-
}
|
|
970
|
-
function sanitizeType(type) {
|
|
971
|
-
return type.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
972
|
-
}
|
|
973
|
-
|
|
974
1839
|
// src/exporters/markdown/markdown-exporter.ts
|
|
975
1840
|
var import_promises3 = require("fs/promises");
|
|
976
1841
|
var import_node_path3 = require("path");
|
|
@@ -986,6 +1851,14 @@ async function exportMarkdownDocs(doc, options) {
|
|
|
986
1851
|
const tablesDir = (0, import_node_path3.join)(options.outDir, "tables");
|
|
987
1852
|
await (0, import_promises3.mkdir)(tablesDir, { recursive: true });
|
|
988
1853
|
const labels = getOutputLabels(options.language);
|
|
1854
|
+
if (doc.tables.length > 0) {
|
|
1855
|
+
const mermaid = getErDiagramMermaid(doc);
|
|
1856
|
+
await (0, import_promises3.writeFile)(
|
|
1857
|
+
(0, import_node_path3.join)(options.outDir, "ER_DIAGRAM.md"),
|
|
1858
|
+
renderErDiagramMarkdown(mermaid, labels),
|
|
1859
|
+
"utf8"
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
989
1862
|
for (const table of doc.tables) {
|
|
990
1863
|
await (0, import_promises3.writeFile)(
|
|
991
1864
|
(0, import_node_path3.join)(tablesDir, `${sanitizeFilename(table.name)}.md`),
|
|
@@ -1034,12 +1907,12 @@ function renderTableDoc(table, doc, labels) {
|
|
|
1034
1907
|
lines.push(`## ${labels.columnsHeading}`);
|
|
1035
1908
|
lines.push("");
|
|
1036
1909
|
lines.push(
|
|
1037
|
-
`| ${labels.
|
|
1910
|
+
`| ${columnDefinitionHeaders(labels).join(" | ")} |`
|
|
1038
1911
|
);
|
|
1039
|
-
lines.push("
|
|
1912
|
+
lines.push(`|${columnDefinitionHeaders(labels).map(() => "--------").join("|")}|`);
|
|
1040
1913
|
for (const col of table.columns) {
|
|
1041
1914
|
lines.push(
|
|
1042
|
-
`| ${
|
|
1915
|
+
`| ${columnDefinitionRow(col, labels).map((value) => escapeMd(value)).join(" | ")} |`
|
|
1043
1916
|
);
|
|
1044
1917
|
}
|
|
1045
1918
|
lines.push("");
|
|
@@ -1071,6 +1944,14 @@ async function exportHtmlDocs(doc, options) {
|
|
|
1071
1944
|
renderIndexPage(doc, labels),
|
|
1072
1945
|
"utf8"
|
|
1073
1946
|
);
|
|
1947
|
+
if (doc.tables.length > 0) {
|
|
1948
|
+
const mermaid = getErDiagramMermaid(doc);
|
|
1949
|
+
await (0, import_promises4.writeFile)(
|
|
1950
|
+
(0, import_node_path4.join)(htmlDir, "er-diagram.html"),
|
|
1951
|
+
renderErDiagramHtmlPage(mermaid, labels),
|
|
1952
|
+
"utf8"
|
|
1953
|
+
);
|
|
1954
|
+
}
|
|
1074
1955
|
for (const table of doc.tables) {
|
|
1075
1956
|
await (0, import_promises4.writeFile)(
|
|
1076
1957
|
(0, import_node_path4.join)(tablesDir, `${sanitizeFilename(table.name)}.html`),
|
|
@@ -1154,7 +2035,7 @@ function pageShell(title, body, fromSubdir = false) {
|
|
|
1154
2035
|
<head>
|
|
1155
2036
|
<meta charset="UTF-8">
|
|
1156
2037
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1157
|
-
<title>${
|
|
2038
|
+
<title>${esc2(title)}</title>
|
|
1158
2039
|
<style>${CSS} </style>
|
|
1159
2040
|
</head>
|
|
1160
2041
|
<body>
|
|
@@ -1171,34 +2052,35 @@ function renderIndexPage(doc, labels) {
|
|
|
1171
2052
|
const fkCount = table.foreignKeys.length;
|
|
1172
2053
|
const fileName = sanitizeFilename(table.name);
|
|
1173
2054
|
tableRows += ` <tr>
|
|
1174
|
-
<td><a href="tables/${fileName}.html">${
|
|
1175
|
-
<td>${
|
|
2055
|
+
<td><a href="tables/${fileName}.html">${esc2(table.name)}</a></td>
|
|
2056
|
+
<td>${esc2(table.comment ?? "")}</td>
|
|
1176
2057
|
<td style="text-align:center">${table.columns.length}</td>
|
|
1177
|
-
<td>${
|
|
2058
|
+
<td>${esc2(pkCols)}</td>
|
|
1178
2059
|
<td style="text-align:center">${fkCount}</td>
|
|
1179
2060
|
</tr>
|
|
1180
2061
|
`;
|
|
1181
2062
|
}
|
|
1182
2063
|
const body = `
|
|
1183
|
-
<h1>${
|
|
2064
|
+
<h1>${esc2(labels.docTitle)}</h1>
|
|
1184
2065
|
<div class="summary">
|
|
1185
|
-
<div class="summary-item"><div class="num">${doc.tables.length}</div><div class="lbl">${
|
|
1186
|
-
<div class="summary-item"><div class="num">${doc.relationships.length}</div><div class="lbl">${
|
|
1187
|
-
<div class="summary-item"><div class="num">${doc.dialect}</div><div class="lbl">${
|
|
2066
|
+
<div class="summary-item"><div class="num">${doc.tables.length}</div><div class="lbl">${esc2(labels.tablesLabel)}</div></div>
|
|
2067
|
+
<div class="summary-item"><div class="num">${doc.relationships.length}</div><div class="lbl">${esc2(labels.relationshipsLabel)}</div></div>
|
|
2068
|
+
<div class="summary-item"><div class="num">${doc.dialect}</div><div class="lbl">${esc2(labels.dialectLabel)}</div></div>
|
|
1188
2069
|
</div>
|
|
1189
|
-
<
|
|
2070
|
+
<p class="back"><a href="er-diagram.html">${esc2(labels.erDiagramHeading)} \u2192</a></p>
|
|
2071
|
+
<h2>${esc2(labels.tableListHeading)}</h2>
|
|
1190
2072
|
<table class="table-list">
|
|
1191
2073
|
<thead><tr>
|
|
1192
|
-
<th>${
|
|
1193
|
-
<th>${
|
|
2074
|
+
<th>${esc2(labels.tableLabel)}</th>
|
|
2075
|
+
<th>${esc2(labels.tableLogicalName)}</th>
|
|
1194
2076
|
<th style="width:70px;text-align:center">Cols</th>
|
|
1195
|
-
<th>${
|
|
2077
|
+
<th>${esc2(labels.primaryKey)}</th>
|
|
1196
2078
|
<th style="width:50px;text-align:center">FK</th>
|
|
1197
2079
|
</tr></thead>
|
|
1198
2080
|
<tbody>
|
|
1199
2081
|
${tableRows} </tbody>
|
|
1200
2082
|
</table>
|
|
1201
|
-
<p class="note">${
|
|
2083
|
+
<p class="note">${esc2(labels.generatedNote)}</p>
|
|
1202
2084
|
`;
|
|
1203
2085
|
return pageShell(labels.docTitle, body);
|
|
1204
2086
|
}
|
|
@@ -1216,39 +2098,34 @@ function renderTablePage(table, doc, labels) {
|
|
|
1216
2098
|
const pkBadge = col.isPrimaryKey ? `<span class="badge badge-pk">PK</span>` : "";
|
|
1217
2099
|
const fkBadge = col.isForeignKey ? `<span class="badge badge-fk">FK</span>` : "";
|
|
1218
2100
|
const rowClass = col.isPrimaryKey ? "pk" : col.isForeignKey ? "fk" : "";
|
|
1219
|
-
const
|
|
1220
|
-
colRows += ` <tr${rowClass ? ` class="${rowClass}"` : ""}><td>${
|
|
2101
|
+
const cells = columnDefinitionRow(col, labels);
|
|
2102
|
+
colRows += ` <tr${rowClass ? ` class="${rowClass}"` : ""}><td>${esc2(cells[0] ?? "")}${pkBadge}${fkBadge}</td>` + cells.slice(1).map((cell) => `<td>${esc2(cell)}</td>`).join("") + `</tr>
|
|
1221
2103
|
`;
|
|
1222
2104
|
}
|
|
1223
2105
|
const body = `
|
|
1224
|
-
<p class="back"><a href="../index.html">\u2190 ${
|
|
1225
|
-
<h1>${
|
|
1226
|
-
<h2>${
|
|
2106
|
+
<p class="back"><a href="../index.html">\u2190 ${esc2(labels.tableListHeading)}</a></p>
|
|
2107
|
+
<h1>${esc2(table.name)}</h1>
|
|
2108
|
+
<h2>${esc2(labels.tableInfoHeading)}</h2>
|
|
1227
2109
|
<table class="meta">
|
|
1228
2110
|
<tbody>
|
|
1229
|
-
<tr><th>${
|
|
1230
|
-
<tr><th>${
|
|
1231
|
-
<tr><th>${
|
|
1232
|
-
<tr><th>${
|
|
1233
|
-
<tr><th>${
|
|
1234
|
-
<tr><th>${
|
|
2111
|
+
<tr><th>${esc2(labels.tablePhysicalName)}</th><td>${esc2(table.name)}</td></tr>
|
|
2112
|
+
<tr><th>${esc2(labels.tableLogicalName)}</th><td>${esc2(table.comment ?? "")}</td></tr>
|
|
2113
|
+
<tr><th>${esc2(labels.schema)}</th><td>${esc2(table.schema ?? "")}</td></tr>
|
|
2114
|
+
<tr><th>${esc2(labels.primaryKey)}</th><td>${esc2(table.primaryKeys.join(", ") || labels.none)}</td></tr>
|
|
2115
|
+
<tr><th>${esc2(labels.foreignKeys)}</th><td>${foreignKeys}</td></tr>
|
|
2116
|
+
<tr><th>${esc2(labels.indexes)}</th><td>${indexText}</td></tr>
|
|
1235
2117
|
</tbody>
|
|
1236
2118
|
</table>
|
|
1237
2119
|
|
|
1238
|
-
<h2>${
|
|
2120
|
+
<h2>${esc2(labels.columnsHeading)}</h2>
|
|
1239
2121
|
<table class="columns">
|
|
1240
2122
|
<thead><tr>
|
|
1241
|
-
|
|
1242
|
-
<th>${esc(labels.logicalName)}</th>
|
|
1243
|
-
<th>${esc(labels.type)}</th>
|
|
1244
|
-
<th>${esc(labels.required)}</th>
|
|
1245
|
-
<th>${esc(labels.defaultValue)}</th>
|
|
1246
|
-
<th>${esc(labels.notes)}</th>
|
|
2123
|
+
${columnDefinitionHeaders(labels).map((header) => `<th>${esc2(header)}</th>`).join("\n ")}
|
|
1247
2124
|
</tr></thead>
|
|
1248
2125
|
<tbody>
|
|
1249
2126
|
${colRows} </tbody>
|
|
1250
2127
|
</table>
|
|
1251
|
-
<p class="note">${
|
|
2128
|
+
<p class="note">${esc2(labels.generatedNote)}</p>
|
|
1252
2129
|
`;
|
|
1253
2130
|
return pageShell(table.name, body);
|
|
1254
2131
|
}
|
|
@@ -1260,7 +2137,7 @@ function collectTableIndexes3(table, doc) {
|
|
|
1260
2137
|
)
|
|
1261
2138
|
];
|
|
1262
2139
|
}
|
|
1263
|
-
function
|
|
2140
|
+
function esc2(text) {
|
|
1264
2141
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1265
2142
|
}
|
|
1266
2143
|
|
|
@@ -1300,6 +2177,53 @@ async function exportWordDocument(doc, options) {
|
|
|
1300
2177
|
children: [new import_docx.TextRun(`${labels.relationshipsLabel}: ${doc.relationships.length}`)]
|
|
1301
2178
|
})
|
|
1302
2179
|
);
|
|
2180
|
+
if (doc.tables.length > 0) {
|
|
2181
|
+
children.push(
|
|
2182
|
+
new import_docx.Paragraph({
|
|
2183
|
+
heading: import_docx.HeadingLevel.HEADING_2,
|
|
2184
|
+
children: [new import_docx.TextRun(labels.erDiagramHeading)]
|
|
2185
|
+
})
|
|
2186
|
+
);
|
|
2187
|
+
try {
|
|
2188
|
+
const { buffer: png, width, height } = await renderErDiagramPng(doc);
|
|
2189
|
+
await (0, import_promises5.writeFile)((0, import_node_path5.join)(options.outDir, "er_diagram.png"), png);
|
|
2190
|
+
const fitted = fitErDiagramToBox(width, height, 620, 900);
|
|
2191
|
+
children.push(
|
|
2192
|
+
new import_docx.Paragraph({
|
|
2193
|
+
children: [
|
|
2194
|
+
new import_docx.ImageRun({
|
|
2195
|
+
data: png,
|
|
2196
|
+
transformation: fitted,
|
|
2197
|
+
type: "png"
|
|
2198
|
+
})
|
|
2199
|
+
]
|
|
2200
|
+
})
|
|
2201
|
+
);
|
|
2202
|
+
} catch {
|
|
2203
|
+
children.push(
|
|
2204
|
+
new import_docx.Paragraph({
|
|
2205
|
+
children: [new import_docx.TextRun(labels.viewErDiagram)]
|
|
2206
|
+
})
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
const mermaid = getErDiagramMermaid(doc);
|
|
2210
|
+
children.push(
|
|
2211
|
+
new import_docx.Paragraph({
|
|
2212
|
+
children: [new import_docx.TextRun({ text: "Mermaid source", bold: true })]
|
|
2213
|
+
})
|
|
2214
|
+
);
|
|
2215
|
+
children.push(
|
|
2216
|
+
new import_docx.Paragraph({
|
|
2217
|
+
children: [
|
|
2218
|
+
new import_docx.TextRun({
|
|
2219
|
+
text: mermaid,
|
|
2220
|
+
font: "Courier New",
|
|
2221
|
+
size: 18
|
|
2222
|
+
})
|
|
2223
|
+
]
|
|
2224
|
+
})
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
1303
2227
|
children.push(
|
|
1304
2228
|
new import_docx.Paragraph({
|
|
1305
2229
|
heading: import_docx.HeadingLevel.HEADING_2,
|
|
@@ -1559,14 +2483,7 @@ function renderTableDetail(table, doc, labels) {
|
|
|
1559
2483
|
return items;
|
|
1560
2484
|
}
|
|
1561
2485
|
function renderColumnsTable(table, labels) {
|
|
1562
|
-
const headerCells =
|
|
1563
|
-
labels.physicalName,
|
|
1564
|
-
labels.logicalName,
|
|
1565
|
-
labels.type,
|
|
1566
|
-
labels.required,
|
|
1567
|
-
labels.defaultValue,
|
|
1568
|
-
labels.notes
|
|
1569
|
-
].map(
|
|
2486
|
+
const headerCells = columnDefinitionHeaders(labels).map(
|
|
1570
2487
|
(h) => new import_docx.TableCell({
|
|
1571
2488
|
children: [
|
|
1572
2489
|
new import_docx.Paragraph({ children: [new import_docx.TextRun({ text: h, bold: true })] })
|
|
@@ -1577,40 +2494,11 @@ function renderColumnsTable(table, labels) {
|
|
|
1577
2494
|
for (const col of table.columns) {
|
|
1578
2495
|
colRows.push(
|
|
1579
2496
|
new import_docx.TableRow({
|
|
1580
|
-
children:
|
|
1581
|
-
new import_docx.TableCell({
|
|
1582
|
-
children: [new import_docx.Paragraph({ children: [new import_docx.TextRun(
|
|
1583
|
-
}),
|
|
1584
|
-
new import_docx.TableCell({
|
|
1585
|
-
children: [
|
|
1586
|
-
new import_docx.Paragraph({ children: [new import_docx.TextRun(col.comment ?? "")] })
|
|
1587
|
-
]
|
|
1588
|
-
}),
|
|
1589
|
-
new import_docx.TableCell({
|
|
1590
|
-
children: [new import_docx.Paragraph({ children: [new import_docx.TextRun(col.type)] })]
|
|
1591
|
-
}),
|
|
1592
|
-
new import_docx.TableCell({
|
|
1593
|
-
children: [
|
|
1594
|
-
new import_docx.Paragraph({
|
|
1595
|
-
children: [new import_docx.TextRun(col.nullable ? labels.no : labels.yes)]
|
|
1596
|
-
})
|
|
1597
|
-
]
|
|
1598
|
-
}),
|
|
1599
|
-
new import_docx.TableCell({
|
|
1600
|
-
children: [
|
|
1601
|
-
new import_docx.Paragraph({
|
|
1602
|
-
children: [new import_docx.TextRun(col.defaultValue ?? "-")]
|
|
1603
|
-
})
|
|
1604
|
-
]
|
|
1605
|
-
}),
|
|
1606
|
-
new import_docx.TableCell({
|
|
1607
|
-
children: [
|
|
1608
|
-
new import_docx.Paragraph({
|
|
1609
|
-
children: [new import_docx.TextRun(col.description?.value ?? "")]
|
|
1610
|
-
})
|
|
1611
|
-
]
|
|
2497
|
+
children: columnDefinitionRow(col, labels).map(
|
|
2498
|
+
(value) => new import_docx.TableCell({
|
|
2499
|
+
children: [new import_docx.Paragraph({ children: [new import_docx.TextRun(value)] })]
|
|
1612
2500
|
})
|
|
1613
|
-
|
|
2501
|
+
)
|
|
1614
2502
|
})
|
|
1615
2503
|
);
|
|
1616
2504
|
}
|