@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/dist/index.js CHANGED
@@ -16,12 +16,17 @@ var reviewTodoSchema = z.object({
16
16
  var columnDocSchema = z.object({
17
17
  name: z.string().min(1),
18
18
  type: z.string().min(1),
19
+ size: z.string().optional(),
19
20
  nullable: z.boolean(),
20
21
  defaultValue: z.string().optional(),
22
+ minValue: z.string().optional(),
23
+ maxValue: z.string().optional(),
24
+ isUnique: z.boolean(),
21
25
  isPrimaryKey: z.boolean(),
22
26
  isForeignKey: z.boolean(),
23
27
  comment: z.string().optional(),
24
- description: enrichedTextSchema.optional()
28
+ description: enrichedTextSchema.optional(),
29
+ constraintNotes: z.array(z.string()).optional()
25
30
  });
26
31
  var foreignKeyDocSchema = z.object({
27
32
  name: z.string().optional(),
@@ -100,7 +105,7 @@ var outputLanguageSchema = z2.enum(["en", "jp"]);
100
105
  var dbdocgenConfigSchema = z2.object({
101
106
  schema: z2.string().default("./schema.sql"),
102
107
  dialect: dialectSchema.optional(),
103
- outDir: z2.string().default("./docs/db"),
108
+ outDir: z2.string().default("./output"),
104
109
  output: z2.object({
105
110
  formats: z2.array(outputFormatSchema).default(["excel", "markdown", "html", "diagram", "word"]),
106
111
  language: outputLanguageSchema.default("en")
@@ -143,6 +148,221 @@ function createWarning(code, message, target) {
143
148
  };
144
149
  }
145
150
 
151
+ // src/parsers/sql/column-meta.ts
152
+ function normalizeColumnType(definition) {
153
+ if (typeof definition !== "object" || definition === null) {
154
+ return { type: String(definition ?? "unknown").toLowerCase() };
155
+ }
156
+ const def = definition;
157
+ const base = String(def.dataType ?? def.type ?? def.name ?? "unknown").toLowerCase();
158
+ if (String(def.dataType ?? "").toUpperCase() === "ENUM") {
159
+ const values = extractEnumValues(def.expr);
160
+ if (values.length > 0) {
161
+ const joined = values.join(", ");
162
+ return {
163
+ type: `${base}(${values.map((v) => `'${v}'`).join(",")})`,
164
+ size: String(values.length)
165
+ };
166
+ }
167
+ }
168
+ if (def.length !== void 0 && def.length !== null) {
169
+ const length = formatAstValue(def.length);
170
+ if (def.scale !== void 0 && def.scale !== null) {
171
+ const scale = formatAstValue(def.scale);
172
+ return {
173
+ type: `${base}(${length},${scale})`,
174
+ size: `${length},${scale}`
175
+ };
176
+ }
177
+ return {
178
+ type: `${base}(${length})`,
179
+ size: length
180
+ };
181
+ }
182
+ const suffix = Array.isArray(def.suffix) ? def.suffix.map((item) => formatAstValue(item)).filter(Boolean).join(" ") : def.suffix ? formatAstValue(def.suffix) : "";
183
+ return {
184
+ type: suffix ? `${base} ${suffix}`.trim() : base
185
+ };
186
+ }
187
+ function extractColumnComment(definition) {
188
+ const comment = definition.comment;
189
+ if (!comment) return void 0;
190
+ const value = comment.value;
191
+ if (value?.value !== void 0) return String(value.value);
192
+ if (comment.value !== void 0 && typeof comment.value === "string") {
193
+ return comment.value;
194
+ }
195
+ return void 0;
196
+ }
197
+ function hasColumnUnique(definition) {
198
+ return definition.unique === "unique" || definition.unique === true;
199
+ }
200
+ function extractColumnConstraintNotes(definition) {
201
+ const notes = [];
202
+ if (definition.auto_increment === "auto_increment" || definition.auto_increment === true) {
203
+ notes.push("AUTO_INCREMENT");
204
+ }
205
+ if (definition.on_update) {
206
+ notes.push(`ON UPDATE ${formatOnUpdate(definition.on_update)}`);
207
+ }
208
+ const generated = definition.generated;
209
+ if (generated) {
210
+ const storage = String(generated.storage_type ?? "virtual").toUpperCase();
211
+ const expression = stringifyGeneratedExpression(generated.expr);
212
+ notes.push(
213
+ expression ? `GENERATED ALWAYS ${storage}: ${expression}` : `GENERATED ALWAYS ${storage}`
214
+ );
215
+ }
216
+ const enumValues = extractEnumValues(definition.definition?.expr);
217
+ if (enumValues.length > 0) {
218
+ notes.push(`ENUM: ${enumValues.join(", ")}`);
219
+ }
220
+ return notes;
221
+ }
222
+ function extractEnumValues(expr) {
223
+ if (!expr || typeof expr !== "object") return [];
224
+ const object = expr;
225
+ if (object.type !== "expr_list" || !Array.isArray(object.value)) return [];
226
+ return object.value.map((item) => {
227
+ if (!item || typeof item !== "object") return "";
228
+ const entry = item;
229
+ return entry.value !== void 0 ? String(entry.value) : "";
230
+ }).filter(Boolean);
231
+ }
232
+ function formatOnUpdate(value) {
233
+ if (!value || typeof value !== "object") return String(value ?? "");
234
+ const object = value;
235
+ if (object.type === "function" && object.name) {
236
+ const name = object.name;
237
+ const parts = Array.isArray(name.name) ? name.name : [];
238
+ return parts.map((part) => String(part.value ?? "")).join("") || "CURRENT_TIMESTAMP";
239
+ }
240
+ return formatAstValue(value);
241
+ }
242
+ function stringifyGeneratedExpression(expr) {
243
+ if (!expr || typeof expr !== "object") return void 0;
244
+ const text = stringifyExpression(expr);
245
+ return text === "check" ? void 0 : text;
246
+ }
247
+ function extractCheckBounds(expression, columnName) {
248
+ const result = {};
249
+ walkCheckExpression(expression, columnName, result);
250
+ return result;
251
+ }
252
+ function walkCheckExpression(expression, columnName, result) {
253
+ if (!expression || typeof expression !== "object") return;
254
+ const expr = expression;
255
+ if (expr.type === "binary_expr") {
256
+ const operator = String(expr.operator ?? "").toUpperCase();
257
+ if (operator === "AND" || operator === "OR") {
258
+ walkCheckExpression(expr.left, columnName, result);
259
+ walkCheckExpression(expr.right, columnName, result);
260
+ return;
261
+ }
262
+ const columnRef = findColumnRef(expr.left) ?? findColumnRef(expr.right);
263
+ if (columnRef !== columnName) return;
264
+ const bound = readBound(expr, columnName);
265
+ if (!bound) return;
266
+ if (bound.kind === "min") {
267
+ result.minValue = mergeBound(result.minValue, bound.value, "max");
268
+ } else {
269
+ result.maxValue = mergeBound(result.maxValue, bound.value, "min");
270
+ }
271
+ return;
272
+ }
273
+ result.expression ??= stringifyExpression(expr);
274
+ }
275
+ function readBound(expr, columnName) {
276
+ const operator = String(expr.operator ?? "");
277
+ const left = expr.left;
278
+ const right = expr.right;
279
+ if (findColumnRef(left) === columnName) {
280
+ if (operator === ">=" || operator === ">") {
281
+ return { kind: "min", value: formatAstValue(right) };
282
+ }
283
+ if (operator === "<=" || operator === "<") {
284
+ return { kind: "max", value: formatAstValue(right) };
285
+ }
286
+ }
287
+ if (findColumnRef(right) === columnName) {
288
+ if (operator === ">=" || operator === ">") {
289
+ return { kind: "max", value: formatAstValue(left) };
290
+ }
291
+ if (operator === "<=" || operator === "<") {
292
+ return { kind: "min", value: formatAstValue(left) };
293
+ }
294
+ }
295
+ return void 0;
296
+ }
297
+ function mergeBound(current, next, pick) {
298
+ if (!current) return next;
299
+ const currentNum = Number(current);
300
+ const nextNum = Number(next);
301
+ if (!Number.isNaN(currentNum) && !Number.isNaN(nextNum)) {
302
+ return pick === "min" ? String(Math.max(currentNum, nextNum)) : String(Math.min(currentNum, nextNum));
303
+ }
304
+ return next;
305
+ }
306
+ function findColumnRef(value) {
307
+ if (!value || typeof value !== "object") return void 0;
308
+ const expr = value;
309
+ if (expr.type === "column_ref" && expr.column) {
310
+ return String(expr.column);
311
+ }
312
+ return void 0;
313
+ }
314
+ function formatAstValue(value) {
315
+ if (value === null || value === void 0) return "";
316
+ if (typeof value === "object") {
317
+ const object = value;
318
+ if (object.value !== void 0) return String(object.value);
319
+ if (object.dataType) return normalizeColumnType(object).type;
320
+ }
321
+ return String(value);
322
+ }
323
+ function stringifyExpression(expr) {
324
+ if (expr.type === "binary_expr") {
325
+ const left = stringifyExpression(expr.left ?? {});
326
+ const right = stringifyExpression(expr.right ?? {});
327
+ return `${left} ${expr.operator} ${right}`.trim();
328
+ }
329
+ if (expr.type === "column_ref") return String(expr.column ?? "");
330
+ if (expr.value !== void 0) return formatAstValue(expr);
331
+ return "check";
332
+ }
333
+ function extractConstraintColumnNames(definition) {
334
+ return extractDeepColumnNames(definition);
335
+ }
336
+ function extractDeepColumnNames(value) {
337
+ if (!Array.isArray(value)) return [];
338
+ return value.map((item) => {
339
+ if (typeof item !== "object" || item === null) return String(item ?? "unknown");
340
+ const object = item;
341
+ if (object.column !== void 0) return String(object.column);
342
+ if (object.expr) return extractDeepColumnName(object.expr);
343
+ return String(object.name ?? "unknown");
344
+ });
345
+ }
346
+ function extractDeepColumnName(value) {
347
+ if (typeof value !== "object" || value === null) {
348
+ return String(value ?? "unknown");
349
+ }
350
+ const object = value;
351
+ if (object.expr) return extractDeepColumnName(object.expr);
352
+ if (object.column && typeof object.column === "object") {
353
+ return extractDeepColumnName(object.column);
354
+ }
355
+ if (object.column !== void 0) return String(object.column);
356
+ return String(object.name ?? "unknown");
357
+ }
358
+ function stringifyCheckDefinition(definition) {
359
+ if (!Array.isArray(definition) || definition.length === 0) return void 0;
360
+ if (definition.length === 1) {
361
+ return stringifyExpression(definition[0]);
362
+ }
363
+ return definition.map((item) => stringifyExpression(item)).filter(Boolean).join("; ");
364
+ }
365
+
146
366
  // src/parsers/sql/sql-normalizer.ts
147
367
  function normalizeSqlAst(ast, dialect) {
148
368
  const statements = Array.isArray(ast) ? ast : [ast];
@@ -164,7 +384,11 @@ function normalizeSqlAst(ast, dialect) {
164
384
  }
165
385
  for (const index of indexes) {
166
386
  const table = tables.find((candidate) => candidate.name === index.table);
167
- table?.indexes.push(index);
387
+ if (!table) continue;
388
+ table.indexes.push(index);
389
+ if (index.unique) {
390
+ markColumnsUnique(table, index.columns, `INDEX ${index.name}`);
391
+ }
168
392
  }
169
393
  return {
170
394
  dialect,
@@ -186,31 +410,49 @@ function normalizeCreateTable(statement) {
186
410
  reviewTodos: []
187
411
  };
188
412
  for (const definition of createDefinitions) {
189
- if (definition.resource === "column") {
190
- const columnName = extractDeepColumnName(definition.column);
191
- const isPrimaryKey = hasPrimaryKey(definition);
192
- const isNotNull = hasNotNull(definition);
193
- table.columns.push({
194
- name: columnName,
195
- type: normalizeType(definition.definition),
196
- nullable: !isNotNull && !isPrimaryKey,
197
- defaultValue: extractDefaultFromDef(definition),
198
- isPrimaryKey,
199
- isForeignKey: false
200
- });
201
- if (isPrimaryKey) table.primaryKeys.push(columnName);
413
+ if (definition.resource !== "column") continue;
414
+ const columnName = extractDeepColumnName2(definition.column);
415
+ const isPrimaryKey = hasPrimaryKey(definition);
416
+ const isNotNull = hasNotNull(definition);
417
+ const { type, size } = normalizeColumnType(definition.definition);
418
+ const check = definition.check;
419
+ const bounds = check?.definition ? extractCheckBounds(check.definition, columnName) : {};
420
+ const constraintNotes = extractColumnConstraintNotes(definition);
421
+ if (check?.definition) {
422
+ const expression = stringifyCheckDefinition(check.definition);
423
+ if (expression && (!bounds.minValue || !bounds.maxValue)) {
424
+ constraintNotes.push(`CHECK: ${expression}`);
425
+ }
202
426
  }
203
- if (definition.resource === "constraint" && isConstraintType(definition.constraint_type, "PRIMARY KEY")) {
204
- table.primaryKeys = extractDeepColumnNames(definition.definition);
427
+ table.columns.push({
428
+ name: columnName,
429
+ type,
430
+ size,
431
+ nullable: !isNotNull && !isPrimaryKey,
432
+ defaultValue: extractDefaultFromDef(definition),
433
+ minValue: bounds.minValue,
434
+ maxValue: bounds.maxValue,
435
+ isUnique: hasColumnUnique(definition),
436
+ isPrimaryKey,
437
+ isForeignKey: false,
438
+ comment: extractColumnComment(definition),
439
+ constraintNotes: constraintNotes.length > 0 ? constraintNotes : void 0
440
+ });
441
+ if (isPrimaryKey) table.primaryKeys.push(columnName);
442
+ }
443
+ for (const definition of createDefinitions) {
444
+ if (definition.resource !== "constraint") continue;
445
+ if (isConstraintType(definition.constraint_type, "PRIMARY KEY")) {
446
+ table.primaryKeys = extractDeepColumnNames2(definition.definition);
205
447
  for (const column of table.columns) {
206
448
  if (table.primaryKeys.includes(column.name)) column.isPrimaryKey = true;
207
449
  }
208
450
  }
209
- if (definition.resource === "constraint" && isConstraintType(definition.constraint_type, "FOREIGN KEY")) {
210
- const columns = extractDeepColumnNames(definition.definition);
451
+ if (isConstraintType(definition.constraint_type, "FOREIGN KEY")) {
452
+ const columns = extractDeepColumnNames2(definition.definition);
211
453
  const refDef = definition.reference_definition;
212
454
  const referencedTable = extractTableName(refDef?.table);
213
- const referencedColumns = extractDeepColumnNames(refDef?.definition);
455
+ const referencedColumns = extractDeepColumnNames2(refDef?.definition);
214
456
  table.foreignKeys.push({
215
457
  name: typeof definition.constraint === "string" ? definition.constraint : void 0,
216
458
  columns,
@@ -221,14 +463,71 @@ function normalizeCreateTable(statement) {
221
463
  if (columns.includes(column.name)) column.isForeignKey = true;
222
464
  }
223
465
  }
466
+ if (isConstraintType(definition.constraint_type, "UNIQUE")) {
467
+ const columns = extractConstraintColumnNames(definition.definition);
468
+ const label = typeof definition.constraint === "string" ? definition.constraint : "UNIQUE";
469
+ markColumnsUnique(table, columns, label);
470
+ }
471
+ if (isConstraintType(definition.constraint_type, "CHECK")) {
472
+ applyTableCheckConstraint(table, definition);
473
+ }
224
474
  }
225
475
  return table;
226
476
  }
477
+ function markColumnsUnique(table, columns, label) {
478
+ const composite = columns.length > 1;
479
+ for (const columnName of columns) {
480
+ const column = table.columns.find((item) => item.name === columnName);
481
+ if (!column) continue;
482
+ column.isUnique = true;
483
+ if (composite) {
484
+ addConstraintNote(
485
+ column,
486
+ `UNIQUE (${label}: ${columns.join(", ")})`
487
+ );
488
+ }
489
+ }
490
+ }
491
+ function applyTableCheckConstraint(table, definition) {
492
+ const expression = stringifyCheckDefinition(definition.definition);
493
+ if (!expression) return;
494
+ const referencedColumns = /* @__PURE__ */ new Set();
495
+ for (const column of table.columns) {
496
+ const bounds = extractCheckBounds(definition.definition, column.name);
497
+ if (bounds.minValue) column.minValue = bounds.minValue;
498
+ if (bounds.maxValue) column.maxValue = bounds.maxValue;
499
+ if (bounds.minValue || bounds.maxValue) {
500
+ referencedColumns.add(column.name);
501
+ continue;
502
+ }
503
+ if (expression.includes(column.name)) {
504
+ referencedColumns.add(column.name);
505
+ }
506
+ }
507
+ if (referencedColumns.size === 0) {
508
+ for (const column of table.columns) {
509
+ addConstraintNote(column, `CHECK: ${expression}`);
510
+ }
511
+ return;
512
+ }
513
+ for (const columnName of referencedColumns) {
514
+ const column = table.columns.find((item) => item.name === columnName);
515
+ if (!column) continue;
516
+ if (!column.minValue && !column.maxValue) {
517
+ addConstraintNote(column, `CHECK: ${expression}`);
518
+ }
519
+ }
520
+ }
521
+ function addConstraintNote(column, note) {
522
+ const notes = column.constraintNotes ?? [];
523
+ if (!notes.includes(note)) notes.push(note);
524
+ column.constraintNotes = notes;
525
+ }
227
526
  function normalizeCreateIndex(statement) {
228
527
  return {
229
528
  name: String(statement.index ?? statement.index_name ?? "unnamed_index"),
230
529
  table: extractTableName(statement.table),
231
- columns: extractDeepColumnNames(
530
+ columns: extractDeepColumnNames2(
232
531
  statement.index_columns ?? statement.columns
233
532
  ),
234
533
  unique: Boolean(statement.unique)
@@ -282,15 +581,15 @@ function extractTableName(value) {
282
581
  }
283
582
  return String(value ?? "unknown");
284
583
  }
285
- function extractDeepColumnName(value) {
584
+ function extractDeepColumnName2(value) {
286
585
  if (typeof value !== "object" || value === null)
287
586
  return String(value ?? "unknown");
288
587
  const object = value;
289
588
  if (object.expr && typeof object.expr === "object") {
290
- return extractDeepColumnName(object.expr);
589
+ return extractDeepColumnName2(object.expr);
291
590
  }
292
591
  if (object.column && typeof object.column === "object") {
293
- return extractDeepColumnName(object.column);
592
+ return extractDeepColumnName2(object.column);
294
593
  }
295
594
  if (object.value !== void 0) {
296
595
  return String(object.value);
@@ -300,18 +599,9 @@ function extractDeepColumnName(value) {
300
599
  }
301
600
  return String(object.name ?? object.tableName ?? "unknown");
302
601
  }
303
- function extractDeepColumnNames(value) {
602
+ function extractDeepColumnNames2(value) {
304
603
  if (!Array.isArray(value)) return [];
305
- return value.map((item) => extractDeepColumnName(item));
306
- }
307
- function normalizeType(value) {
308
- if (typeof value === "object" && value !== null) {
309
- const object = value;
310
- return String(
311
- object.dataType ?? object.type ?? object.name ?? "unknown"
312
- ).toLowerCase();
313
- }
314
- return String(value ?? "unknown").toLowerCase();
604
+ return value.map((item) => extractDeepColumnName2(item));
315
605
  }
316
606
  function hasPrimaryKey(def) {
317
607
  if (def.primary_key) return true;
@@ -506,8 +796,8 @@ function dialectBias(dialect, requestedDialect, detectedDialect) {
506
796
  }
507
797
 
508
798
  // src/exporters/excel/excel-exporter.ts
509
- import { mkdir } from "fs/promises";
510
- import { join } from "path";
799
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
800
+ import { join as join2 } from "path";
511
801
  import ExcelJS from "exceljs";
512
802
 
513
803
  // src/exporters/shared/output-labels.ts
@@ -530,6 +820,10 @@ var LABELS = {
530
820
  type: "Type",
531
821
  required: "Required",
532
822
  defaultValue: "Default Value",
823
+ size: "Size",
824
+ minValue: "Min",
825
+ maxValue: "Max",
826
+ unique: "Unique",
533
827
  notes: "Notes",
534
828
  yes: "Yes",
535
829
  no: "No",
@@ -558,7 +852,15 @@ var LABELS = {
558
852
  rowNo: "#",
559
853
  backToOverview: "\u2190 Overview",
560
854
  pkMarker: "PK",
561
- fkMarker: "FK"
855
+ fkMarker: "FK",
856
+ erDiagramHeading: "ER Diagram",
857
+ erDiagramSheet: "ER Diagram",
858
+ viewErDiagram: "View interactive ER diagram (html/er-diagram.html)",
859
+ zoomIn: "Zoom in",
860
+ zoomOut: "Zoom out",
861
+ zoomReset: "Reset",
862
+ zoomFit: "Fit",
863
+ panZoomHint: "Drag to pan \xB7 Scroll to zoom"
562
864
  },
563
865
  jp: {
564
866
  docTitle: "Database Documentation",
@@ -578,6 +880,10 @@ var LABELS = {
578
880
  type: "\u578B",
579
881
  required: "\u5FC5\u9808",
580
882
  defaultValue: "\u30C7\u30D5\u30A9\u30EB\u30C8\u5024",
883
+ size: "\u6841\u6570",
884
+ minValue: "\u6700\u5C0F\u5024",
885
+ maxValue: "\u6700\u5927\u5024",
886
+ unique: "\u4E00\u610F",
581
887
  notes: "\u5099\u8003",
582
888
  yes: "Yes",
583
889
  no: "No",
@@ -606,13 +912,582 @@ var LABELS = {
606
912
  rowNo: "No.",
607
913
  backToOverview: "\u2190 \u4E00\u89A7",
608
914
  pkMarker: "PK",
609
- fkMarker: "FK"
915
+ fkMarker: "FK",
916
+ erDiagramHeading: "ER Diagram",
917
+ erDiagramSheet: "ER Diagram",
918
+ viewErDiagram: "\u30A4\u30F3\u30BF\u30E9\u30AF\u30C6\u30A3\u30D6ER\u56F3 (html/er-diagram.html)",
919
+ zoomIn: "\u62E1\u5927",
920
+ zoomOut: "\u7E2E\u5C0F",
921
+ zoomReset: "\u30EA\u30BB\u30C3\u30C8",
922
+ zoomFit: "\u5168\u4F53\u8868\u793A",
923
+ panZoomHint: "\u30C9\u30E9\u30C3\u30B0\u3067\u79FB\u52D5 \xB7 \u30B9\u30AF\u30ED\u30FC\u30EB\u3067\u62E1\u5927\u7E2E\u5C0F"
610
924
  }
611
925
  };
612
926
  function getOutputLabels(language = "en") {
613
927
  return LABELS[language];
614
928
  }
615
929
 
930
+ // src/exporters/shared/column-definition.ts
931
+ var A5_COLUMN_COUNT = 10;
932
+ function columnDefinitionHeaders(labels) {
933
+ return [
934
+ labels.physicalName,
935
+ labels.logicalName,
936
+ labels.type,
937
+ labels.size,
938
+ labels.required,
939
+ labels.defaultValue,
940
+ labels.minValue,
941
+ labels.maxValue,
942
+ labels.unique,
943
+ labels.notes
944
+ ];
945
+ }
946
+ function formatColumnNotes(column, labels) {
947
+ const parts = [];
948
+ if (column.isPrimaryKey) parts.push(labels.pkMarker);
949
+ if (column.isForeignKey) parts.push(labels.fkMarker);
950
+ if (column.constraintNotes?.length) parts.push(...column.constraintNotes);
951
+ if (column.description?.value) parts.push(column.description.value);
952
+ return parts.join(", ") || labels.none;
953
+ }
954
+ function columnDefinitionRow(column, labels) {
955
+ return [
956
+ column.name,
957
+ column.comment ?? "",
958
+ column.type,
959
+ column.size ?? labels.none,
960
+ column.nullable ? labels.no : labels.yes,
961
+ column.defaultValue ?? labels.none,
962
+ column.minValue ?? labels.none,
963
+ column.maxValue ?? labels.none,
964
+ column.isUnique ? labels.yes : labels.no,
965
+ formatColumnNotes(column, labels)
966
+ ];
967
+ }
968
+
969
+ // src/exporters/diagram/mermaid-exporter.ts
970
+ import { mkdir, writeFile } from "fs/promises";
971
+ import { join } from "path";
972
+ async function exportMermaidDiagram(doc, options) {
973
+ await mkdir(options.outDir, { recursive: true });
974
+ await writeFile(
975
+ join(options.outDir, "er_diagram.mmd"),
976
+ renderMermaid(doc),
977
+ "utf8"
978
+ );
979
+ }
980
+ function renderMermaid(doc) {
981
+ const lines = ["erDiagram"];
982
+ for (const warning of doc.warnings) {
983
+ const target = warning.target ? ` (${warning.target})` : "";
984
+ lines.push(
985
+ ` %% WARNING [${warning.severity}] ${warning.code}${target}: ${warning.message}`
986
+ );
987
+ }
988
+ for (const table of doc.tables) {
989
+ for (const todo of table.reviewTodos) {
990
+ lines.push(` %% TODO [${todo.type}] ${todo.target}: ${todo.issue}`);
991
+ }
992
+ lines.push(` ${table.name} {`);
993
+ for (const column of table.columns) {
994
+ const markers = [
995
+ column.isPrimaryKey ? "PK" : "",
996
+ column.isForeignKey ? "FK" : ""
997
+ ].filter(Boolean).join(" ");
998
+ lines.push(
999
+ ` ${sanitizeType(column.type)} ${column.name}${markers ? ` "${markers}"` : ""}`
1000
+ );
1001
+ }
1002
+ lines.push(" }");
1003
+ }
1004
+ for (const relationship of doc.relationships.filter(
1005
+ (item) => item.source === "schema"
1006
+ )) {
1007
+ lines.push(
1008
+ ` ${relationship.toTable} ||--o{ ${relationship.fromTable} : "${relationship.constraintName ?? relationship.fromColumn}"`
1009
+ );
1010
+ }
1011
+ return `${lines.join("\n")}
1012
+ `;
1013
+ }
1014
+ function sanitizeType(type) {
1015
+ return type.replace(/[^a-zA-Z0-9_]/g, "_");
1016
+ }
1017
+
1018
+ // src/exporters/diagram/er-diagram-embed.ts
1019
+ function getErDiagramMermaid(doc) {
1020
+ return renderMermaid(doc);
1021
+ }
1022
+ function renderErDiagramHtmlPage(mermaidSource, labels) {
1023
+ const escaped = mermaidSource.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1024
+ return `<!DOCTYPE html>
1025
+ <html lang="en">
1026
+ <head>
1027
+ <meta charset="UTF-8">
1028
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1029
+ <title>${esc(labels.erDiagramHeading)}</title>
1030
+ <style>
1031
+ body { margin: 0; font-family: "Yu Gothic UI", "Meiryo", Arial, sans-serif; background: #f3f4f6; }
1032
+ .toolbar {
1033
+ background: #4472c4; color: #fff; padding: 10px 16px;
1034
+ display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
1035
+ }
1036
+ .toolbar a { color: #fff; text-decoration: underline; }
1037
+ .toolbar .spacer { flex: 1; }
1038
+ .toolbar .hint { opacity: 0.9; font-size: 13px; }
1039
+ .toolbar button {
1040
+ background: #fff; color: #2f5597; border: none; border-radius: 4px;
1041
+ padding: 6px 12px; font-size: 13px; cursor: pointer; font-weight: 600;
1042
+ }
1043
+ .toolbar button:hover { background: #e8eef8; }
1044
+ .viewport {
1045
+ position: relative; height: calc(100vh - 52px); margin: 12px;
1046
+ background: #fff; border: 1px solid #bfc7d4; border-radius: 4px;
1047
+ overflow: hidden; cursor: grab; touch-action: none;
1048
+ }
1049
+ .viewport.dragging { cursor: grabbing; }
1050
+ .canvas {
1051
+ position: absolute; left: 0; top: 0; transform-origin: 0 0;
1052
+ padding: 24px;
1053
+ }
1054
+ .mermaid { min-width: 320px; }
1055
+ .mermaid svg { max-width: none !important; height: auto !important; }
1056
+ </style>
1057
+ </head>
1058
+ <body>
1059
+ <div class="toolbar">
1060
+ <strong>${esc(labels.erDiagramHeading)}</strong>
1061
+ <a href="index.html">\u2190 ${esc(labels.tableListHeading)}</a>
1062
+ <span class="spacer"></span>
1063
+ <span class="hint">${esc(labels.panZoomHint)}</span>
1064
+ <button type="button" id="zoom-out" title="${esc(labels.zoomOut)}">\u2212</button>
1065
+ <button type="button" id="zoom-reset" title="${esc(labels.zoomReset)}">${esc(labels.zoomReset)}</button>
1066
+ <button type="button" id="zoom-in" title="${esc(labels.zoomIn)}">+</button>
1067
+ <button type="button" id="zoom-fit" title="${esc(labels.zoomFit)}">${esc(labels.zoomFit)}</button>
1068
+ </div>
1069
+ <div class="viewport" id="viewport">
1070
+ <div class="canvas" id="canvas">
1071
+ <pre class="mermaid">${escaped}</pre>
1072
+ </div>
1073
+ </div>
1074
+ <script type="module">
1075
+ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
1076
+
1077
+ mermaid.initialize({
1078
+ startOnLoad: false,
1079
+ theme: "default",
1080
+ er: { useMaxWidth: false }
1081
+ });
1082
+
1083
+ await mermaid.run({ querySelector: ".mermaid" });
1084
+ setupPanZoom(
1085
+ document.getElementById("viewport"),
1086
+ document.getElementById("canvas")
1087
+ );
1088
+
1089
+ function setupPanZoom(viewport, canvas) {
1090
+ let scale = 1;
1091
+ let tx = 40;
1092
+ let ty = 40;
1093
+ let dragging = false;
1094
+ let lastX = 0;
1095
+ let lastY = 0;
1096
+
1097
+ function apply() {
1098
+ canvas.style.transform = "translate(" + tx + "px," + ty + "px) scale(" + scale + ")";
1099
+ }
1100
+
1101
+ function zoomAt(factor, cx, cy) {
1102
+ const next = Math.min(4, Math.max(0.15, scale * factor));
1103
+ const ratio = next / scale;
1104
+ tx = cx - (cx - tx) * ratio;
1105
+ ty = cy - (cy - ty) * ratio;
1106
+ scale = next;
1107
+ apply();
1108
+ }
1109
+
1110
+ function fitToView() {
1111
+ const svg = canvas.querySelector("svg");
1112
+ if (!svg) return;
1113
+ const box = svg.getBBox();
1114
+ const pad = 32;
1115
+ const vw = viewport.clientWidth;
1116
+ const vh = viewport.clientHeight;
1117
+ scale = Math.min(
1118
+ (vw - pad * 2) / Math.max(box.width, 1),
1119
+ (vh - pad * 2) / Math.max(box.height, 1),
1120
+ 1.5
1121
+ );
1122
+ tx = (vw - box.width * scale) / 2 - box.x * scale;
1123
+ ty = (vh - box.height * scale) / 2 - box.y * scale;
1124
+ apply();
1125
+ }
1126
+
1127
+ viewport.addEventListener("wheel", (e) => {
1128
+ e.preventDefault();
1129
+ const rect = viewport.getBoundingClientRect();
1130
+ const cx = e.clientX - rect.left;
1131
+ const cy = e.clientY - rect.top;
1132
+ zoomAt(e.deltaY < 0 ? 1.12 : 0.89, cx, cy);
1133
+ }, { passive: false });
1134
+
1135
+ viewport.addEventListener("mousedown", (e) => {
1136
+ if (e.button !== 0) return;
1137
+ dragging = true;
1138
+ lastX = e.clientX;
1139
+ lastY = e.clientY;
1140
+ viewport.classList.add("dragging");
1141
+ });
1142
+
1143
+ window.addEventListener("mousemove", (e) => {
1144
+ if (!dragging) return;
1145
+ tx += e.clientX - lastX;
1146
+ ty += e.clientY - lastY;
1147
+ lastX = e.clientX;
1148
+ lastY = e.clientY;
1149
+ apply();
1150
+ });
1151
+
1152
+ window.addEventListener("mouseup", () => {
1153
+ dragging = false;
1154
+ viewport.classList.remove("dragging");
1155
+ });
1156
+
1157
+ document.getElementById("zoom-in").addEventListener("click", () => {
1158
+ zoomAt(1.2, viewport.clientWidth / 2, viewport.clientHeight / 2);
1159
+ });
1160
+ document.getElementById("zoom-out").addEventListener("click", () => {
1161
+ zoomAt(1 / 1.2, viewport.clientWidth / 2, viewport.clientHeight / 2);
1162
+ });
1163
+ document.getElementById("zoom-reset").addEventListener("click", () => {
1164
+ scale = 1;
1165
+ tx = 40;
1166
+ ty = 40;
1167
+ apply();
1168
+ });
1169
+ document.getElementById("zoom-fit").addEventListener("click", fitToView);
1170
+
1171
+ fitToView();
1172
+ }
1173
+ </script>
1174
+ </body>
1175
+ </html>`;
1176
+ }
1177
+ function renderErDiagramMarkdown(mermaidSource, labels) {
1178
+ return [
1179
+ `# ${labels.erDiagramHeading}`,
1180
+ "",
1181
+ "```mermaid",
1182
+ mermaidSource.trimEnd(),
1183
+ "```",
1184
+ ""
1185
+ ].join("\n");
1186
+ }
1187
+ function esc(text) {
1188
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1189
+ }
1190
+
1191
+ // src/exporters/diagram/er-diagram-layout.ts
1192
+ import ELK from "elkjs/lib/elk.bundled.js";
1193
+ var BOX_W = 200;
1194
+ var HEADER_H = 28;
1195
+ var LINE_H = 14;
1196
+ var MAX_COLS_SHOWN = 6;
1197
+ var COMPACT_THRESHOLD = 18;
1198
+ var PAD = 24;
1199
+ var CLUSTER_GAP = 56;
1200
+ function isCompactLayout(tableCount) {
1201
+ return tableCount >= COMPACT_THRESHOLD;
1202
+ }
1203
+ function getVisibleErColumns(table) {
1204
+ const prioritized = [
1205
+ ...table.columns.filter((column) => column.isPrimaryKey),
1206
+ ...table.columns.filter(
1207
+ (column) => column.isForeignKey && !column.isPrimaryKey
1208
+ ),
1209
+ ...table.columns.filter(
1210
+ (column) => !column.isPrimaryKey && !column.isForeignKey
1211
+ )
1212
+ ];
1213
+ const unique = prioritized.filter(
1214
+ (column, index, columns) => columns.findIndex((item) => item.name === column.name) === index
1215
+ );
1216
+ return unique.slice(0, MAX_COLS_SHOWN);
1217
+ }
1218
+ function measureTableBox(table, _compact = false) {
1219
+ const visible = getVisibleErColumns(table);
1220
+ const extra = table.columns.length > visible.length ? 1 : 0;
1221
+ return {
1222
+ w: BOX_W,
1223
+ h: HEADER_H + (visible.length + extra) * LINE_H + 8
1224
+ };
1225
+ }
1226
+ function buildAdjacency(doc) {
1227
+ const names = new Set(doc.tables.map((t) => t.name));
1228
+ const adj = /* @__PURE__ */ new Map();
1229
+ for (const name of names) adj.set(name, /* @__PURE__ */ new Set());
1230
+ for (const rel of doc.relationships.filter((r) => r.source === "schema")) {
1231
+ if (!names.has(rel.fromTable) || !names.has(rel.toTable)) continue;
1232
+ adj.get(rel.fromTable).add(rel.toTable);
1233
+ adj.get(rel.toTable).add(rel.fromTable);
1234
+ }
1235
+ return adj;
1236
+ }
1237
+ function connectedComponents(tableNames, adj) {
1238
+ const visited = /* @__PURE__ */ new Set();
1239
+ const components = [];
1240
+ for (const name of tableNames) {
1241
+ if (visited.has(name)) continue;
1242
+ const stack = [name];
1243
+ const component = [];
1244
+ visited.add(name);
1245
+ while (stack.length > 0) {
1246
+ const current = stack.pop();
1247
+ component.push(current);
1248
+ for (const next of adj.get(current) ?? []) {
1249
+ if (!visited.has(next)) {
1250
+ visited.add(next);
1251
+ stack.push(next);
1252
+ }
1253
+ }
1254
+ }
1255
+ component.sort();
1256
+ components.push(component);
1257
+ }
1258
+ return components.sort((a, b) => b.length - a.length);
1259
+ }
1260
+ function sectionToPoints(section) {
1261
+ return [section.startPoint, ...section.bendPoints ?? [], section.endPoint];
1262
+ }
1263
+ function extractEdges(layouted) {
1264
+ const edges = [];
1265
+ for (const edge of layouted.edges ?? []) {
1266
+ for (const section of edge.sections ?? []) {
1267
+ edges.push({
1268
+ id: edge.id,
1269
+ points: sectionToPoints(section)
1270
+ });
1271
+ }
1272
+ }
1273
+ return edges;
1274
+ }
1275
+ async function layoutComponent(doc, tableNames, compact) {
1276
+ const elk = new ELK();
1277
+ const nameSet = new Set(tableNames);
1278
+ const tables = doc.tables.filter((t) => nameSet.has(t.name));
1279
+ const direction = tables.length >= 8 ? "DOWN" : "RIGHT";
1280
+ const children = tables.map((table) => {
1281
+ const { w, h } = measureTableBox(table, compact);
1282
+ return { id: table.name, width: w, height: h };
1283
+ });
1284
+ const edges = [];
1285
+ const seen = /* @__PURE__ */ new Set();
1286
+ for (const rel of doc.relationships.filter((r) => r.source === "schema")) {
1287
+ if (!nameSet.has(rel.fromTable) || !nameSet.has(rel.toTable)) continue;
1288
+ const key = `${rel.fromTable}->${rel.toTable}`;
1289
+ if (seen.has(key)) continue;
1290
+ seen.add(key);
1291
+ edges.push({
1292
+ id: key,
1293
+ sources: [rel.fromTable],
1294
+ targets: [rel.toTable]
1295
+ });
1296
+ }
1297
+ const graph = {
1298
+ id: "root",
1299
+ layoutOptions: {
1300
+ "elk.algorithm": "layered",
1301
+ "elk.direction": direction,
1302
+ "elk.edgeRouting": "ORTHOGONAL",
1303
+ "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
1304
+ "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP",
1305
+ "elk.layered.spacing.nodeNodeBetweenLayers": compact ? "56" : "80",
1306
+ "elk.spacing.nodeNode": compact ? "32" : "48",
1307
+ "elk.spacing.edgeNode": "24",
1308
+ "elk.padding": `[top=${PAD},left=${PAD},bottom=${PAD},right=${PAD}]`
1309
+ },
1310
+ children,
1311
+ edges
1312
+ };
1313
+ const layouted = await elk.layout(graph);
1314
+ const boxes = /* @__PURE__ */ new Map();
1315
+ let minX = Infinity;
1316
+ let minY = Infinity;
1317
+ let maxX = -Infinity;
1318
+ let maxY = -Infinity;
1319
+ for (const child of layouted.children ?? []) {
1320
+ const table = tables.find((t) => t.name === child.id);
1321
+ if (!table) continue;
1322
+ const { w, h } = measureTableBox(table, compact);
1323
+ const x = child.x ?? 0;
1324
+ const y = child.y ?? 0;
1325
+ const box = { x, y, w, h };
1326
+ boxes.set(child.id, box);
1327
+ minX = Math.min(minX, x);
1328
+ minY = Math.min(minY, y);
1329
+ maxX = Math.max(maxX, x + w);
1330
+ maxY = Math.max(maxY, y + h);
1331
+ }
1332
+ const width = Math.ceil(maxX - minX + PAD * 2);
1333
+ const height = Math.ceil(maxY - minY + PAD * 2);
1334
+ const dx = PAD - minX;
1335
+ const dy = PAD - minY;
1336
+ for (const [name, box] of boxes) {
1337
+ boxes.set(name, { x: box.x + dx, y: box.y + dy, w: box.w, h: box.h });
1338
+ }
1339
+ const shiftedEdges = extractEdges(layouted).map((edge) => ({
1340
+ ...edge,
1341
+ points: edge.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
1342
+ }));
1343
+ return { boxes, edges: shiftedEdges, width, height };
1344
+ }
1345
+ function shiftLayout(boxes, edges, offsetX, offsetY) {
1346
+ for (const [name, box] of boxes) {
1347
+ boxes.set(name, { ...box, x: box.x + offsetX, y: box.y + offsetY });
1348
+ }
1349
+ for (const edge of edges) {
1350
+ for (const p of edge.points) {
1351
+ p.x += offsetX;
1352
+ p.y += offsetY;
1353
+ }
1354
+ }
1355
+ }
1356
+ async function layoutErDiagram(doc) {
1357
+ const tables = doc.tables;
1358
+ if (tables.length === 0) {
1359
+ return {
1360
+ boxes: /* @__PURE__ */ new Map(),
1361
+ edges: [],
1362
+ compact: false,
1363
+ width: 400,
1364
+ height: 80
1365
+ };
1366
+ }
1367
+ const compact = isCompactLayout(tables.length);
1368
+ const adj = buildAdjacency(doc);
1369
+ const components = connectedComponents(
1370
+ tables.map((t) => t.name),
1371
+ adj
1372
+ );
1373
+ const mergedBoxes = /* @__PURE__ */ new Map();
1374
+ const mergedEdges = [];
1375
+ const clusterCols = components.length <= 1 ? 1 : components.length <= 4 ? 2 : 3;
1376
+ let tileX = 0;
1377
+ let tileY = 0;
1378
+ let rowHeight = 0;
1379
+ let maxWidth = PAD;
1380
+ let maxHeight = PAD;
1381
+ for (const [i, component] of components.entries()) {
1382
+ const laid = await layoutComponent(doc, component, compact);
1383
+ shiftLayout(laid.boxes, laid.edges, tileX, tileY);
1384
+ for (const [name, box] of laid.boxes) mergedBoxes.set(name, box);
1385
+ mergedEdges.push(...laid.edges);
1386
+ rowHeight = Math.max(rowHeight, laid.height);
1387
+ tileX += laid.width + CLUSTER_GAP;
1388
+ maxWidth = Math.max(maxWidth, tileX);
1389
+ maxHeight = Math.max(maxHeight, tileY + laid.height);
1390
+ if ((i + 1) % clusterCols === 0) {
1391
+ tileX = 0;
1392
+ tileY += rowHeight + CLUSTER_GAP;
1393
+ rowHeight = 0;
1394
+ }
1395
+ }
1396
+ return {
1397
+ boxes: mergedBoxes,
1398
+ edges: mergedEdges,
1399
+ compact,
1400
+ width: Math.ceil(maxWidth),
1401
+ height: Math.ceil(maxHeight + PAD)
1402
+ };
1403
+ }
1404
+
1405
+ // src/exporters/diagram/er-diagram-svg.ts
1406
+ async function renderErDiagramSvg(doc) {
1407
+ const tables = doc.tables;
1408
+ if (tables.length === 0) {
1409
+ 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>`;
1410
+ }
1411
+ const layout = await layoutErDiagram(doc);
1412
+ return buildErDiagramSvg(doc, layout);
1413
+ }
1414
+ async function renderErDiagramPng(doc) {
1415
+ const tables = doc.tables;
1416
+ if (tables.length === 0) {
1417
+ const svg2 = await renderErDiagramSvg(doc);
1418
+ const sharp2 = (await import("sharp")).default;
1419
+ const buffer2 = await sharp2(Buffer.from(svg2)).png().toBuffer();
1420
+ return { buffer: buffer2, width: 400, height: 80 };
1421
+ }
1422
+ const layout = await layoutErDiagram(doc);
1423
+ const svg = buildErDiagramSvg(doc, layout);
1424
+ const sharp = (await import("sharp")).default;
1425
+ const buffer = await sharp(Buffer.from(svg)).png().toBuffer();
1426
+ return { buffer, width: layout.width, height: layout.height };
1427
+ }
1428
+ function buildErDiagramSvg(doc, layout) {
1429
+ const { boxes, edges, width, height } = layout;
1430
+ const parts = [
1431
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" font-family="Arial,sans-serif" font-size="11">`,
1432
+ `<rect width="100%" height="100%" fill="#ffffff"/>`,
1433
+ `<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>`,
1434
+ `<g class="edges">`
1435
+ ];
1436
+ for (const edge of edges) {
1437
+ if (edge.points.length < 2) continue;
1438
+ const d = pointsToPath(edge.points);
1439
+ parts.push(
1440
+ `<path d="${d}" fill="none" stroke="#7d96b8" stroke-width="1.25" marker-end="url(#arrow)"/>`
1441
+ );
1442
+ }
1443
+ parts.push(`</g><g class="nodes">`);
1444
+ for (const table of doc.tables) {
1445
+ const box = boxes.get(table.name);
1446
+ parts.push(...renderTableBox(table, box));
1447
+ }
1448
+ parts.push("</g></svg>");
1449
+ return parts.join("");
1450
+ }
1451
+ function pointsToPath(points) {
1452
+ return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(" ");
1453
+ }
1454
+ function renderTableBox(table, box) {
1455
+ const parts = [
1456
+ `<rect x="${box.x}" y="${box.y}" width="${box.w}" height="${box.h}" fill="#f8fafc" stroke="#4472c4" stroke-width="1.5" rx="4"/>`,
1457
+ `<rect x="${box.x}" y="${box.y}" width="${box.w}" height="${HEADER_H}" fill="#4472c4" rx="4"/>`,
1458
+ `<rect x="${box.x}" y="${box.y + HEADER_H - 4}" width="${box.w}" height="4" fill="#4472c4"/>`,
1459
+ `<text x="${box.x + 8}" y="${box.y + 18}" fill="#ffffff" font-weight="bold">${escapeXml(table.name)}</text>`
1460
+ ];
1461
+ let cy = box.y + HEADER_H + 14;
1462
+ const visible = getVisibleErColumns(table);
1463
+ for (const col of visible) {
1464
+ const marker = col.isPrimaryKey ? " PK" : col.isForeignKey ? " FK" : "";
1465
+ parts.push(
1466
+ `<text x="${box.x + 8}" y="${cy}" fill="#333333">${escapeXml(col.name)} : ${escapeXml(shortType(col.type))}${marker}</text>`
1467
+ );
1468
+ cy += LINE_H;
1469
+ }
1470
+ if (table.columns.length > visible.length) {
1471
+ parts.push(
1472
+ `<text x="${box.x + 8}" y="${cy}" fill="#666666">... +${table.columns.length - visible.length} more</text>`
1473
+ );
1474
+ }
1475
+ return parts;
1476
+ }
1477
+ function shortType(type) {
1478
+ return type.length > 18 ? `${type.slice(0, 15)}...` : type;
1479
+ }
1480
+ function escapeXml(text) {
1481
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1482
+ }
1483
+ function fitErDiagramToBox(width, height, maxWidth, maxHeight) {
1484
+ const scale = Math.min(maxWidth / width, maxHeight / height, 1);
1485
+ return {
1486
+ width: Math.max(1, Math.round(width * scale)),
1487
+ height: Math.max(1, Math.round(height * scale))
1488
+ };
1489
+ }
1490
+
616
1491
  // src/exporters/excel/excel-exporter.ts
617
1492
  var COLOR = {
618
1493
  headerBg: "FF4472C4",
@@ -630,7 +1505,7 @@ var COLOR = {
630
1505
  };
631
1506
  var COL_COUNT = 7;
632
1507
  async function exportExcelDictionary(doc, options) {
633
- await mkdir(options.outDir, { recursive: true });
1508
+ await mkdir2(options.outDir, { recursive: true });
634
1509
  const workbook = new ExcelJS.Workbook();
635
1510
  const labels = getOutputLabels(options.language);
636
1511
  const sheetNames = /* @__PURE__ */ new Map();
@@ -638,13 +1513,16 @@ async function exportExcelDictionary(doc, options) {
638
1513
  sheetNames.set(table.name, buildSheetName(table.name, sheetNames));
639
1514
  }
640
1515
  addOverviewSheet(workbook, doc, labels, sheetNames);
1516
+ if (doc.tables.length > 0) {
1517
+ await addErDiagramSheet(workbook, doc, labels, options.outDir);
1518
+ }
641
1519
  for (const table of doc.tables) {
642
1520
  const sheetName = sheetNames.get(table.name);
643
1521
  const sheet = workbook.addWorksheet(sheetName);
644
1522
  populateTableSheet(sheet, table, doc, labels);
645
1523
  }
646
1524
  await workbook.xlsx.writeFile(
647
- join(options.outDir, "database_dictionary.xlsx")
1525
+ join2(options.outDir, "database_dictionary.xlsx")
648
1526
  );
649
1527
  }
650
1528
  function addOverviewSheet(workbook, doc, labels, sheetNames) {
@@ -725,6 +1603,51 @@ function addOverviewSheet(workbook, doc, labels, sheetNames) {
725
1603
  };
726
1604
  sheet.views = [{ state: "frozen", ySplit: headerRowNum }];
727
1605
  }
1606
+ async function addErDiagramSheet(workbook, doc, labels, outDir) {
1607
+ const sheet = workbook.addWorksheet(labels.erDiagramSheet);
1608
+ sheet.mergeCells(1, 1, 1, 6);
1609
+ const titleCell = sheet.getCell(1, 1);
1610
+ titleCell.value = labels.erDiagramHeading;
1611
+ titleCell.font = { bold: true, size: 14, color: { argb: COLOR.overviewFg } };
1612
+ titleCell.fill = solidFill(COLOR.overviewBg);
1613
+ titleCell.alignment = { horizontal: "center", vertical: "middle" };
1614
+ sheet.getRow(1).height = 28;
1615
+ let nextRow = 3;
1616
+ try {
1617
+ const { buffer: png, width, height } = await renderErDiagramPng(doc);
1618
+ await writeFile2(join2(outDir, "er_diagram.png"), png);
1619
+ const imageId = workbook.addImage({
1620
+ base64: png.toString("base64"),
1621
+ extension: "png"
1622
+ });
1623
+ const fitted = fitErDiagramToBox(width, height, 1100, 1200);
1624
+ sheet.addImage(imageId, {
1625
+ tl: { col: 0, row: 2 },
1626
+ ext: fitted
1627
+ });
1628
+ nextRow = Math.max(28, Math.ceil(fitted.height / 18) + 4);
1629
+ } catch {
1630
+ sheet.getCell(3, 1).value = labels.viewErDiagram;
1631
+ nextRow = 5;
1632
+ }
1633
+ const mermaid = getErDiagramMermaid(doc);
1634
+ sheet.getCell(nextRow, 1).value = "Mermaid source";
1635
+ sheet.getCell(nextRow, 1).font = { bold: true, color: { argb: COLOR.metaFg } };
1636
+ nextRow += 1;
1637
+ sheet.mergeCells(nextRow, 1, nextRow + 20, 6);
1638
+ const sourceCell = sheet.getCell(nextRow, 1);
1639
+ sourceCell.value = mermaid;
1640
+ sourceCell.alignment = { wrapText: true, vertical: "top" };
1641
+ sourceCell.font = { name: "Courier New", size: 9 };
1642
+ sheet.columns = [
1643
+ { width: 24 },
1644
+ { width: 24 },
1645
+ { width: 24 },
1646
+ { width: 24 },
1647
+ { width: 24 },
1648
+ { width: 24 }
1649
+ ];
1650
+ }
728
1651
  function populateTableSheet(sheet, table, doc, labels) {
729
1652
  const indexes = collectTableIndexes(table, doc);
730
1653
  sheet.mergeCells(1, 1, 1, 6);
@@ -765,48 +1688,39 @@ function populateTableSheet(sheet, table, doc, labels) {
765
1688
  row.getCell(2).alignment = { wrapText: true, vertical: "top" };
766
1689
  }
767
1690
  sheet.addRow([]);
768
- const headerRow = sheet.addRow([
769
- labels.physicalName,
770
- labels.logicalName,
771
- labels.type,
772
- labels.required,
773
- labels.defaultValue,
774
- labels.notes
775
- ]);
1691
+ const headerRow = sheet.addRow(columnDefinitionHeaders(labels));
776
1692
  styleColorRow(headerRow, COLOR.headerBg, COLOR.headerFg);
777
- applyBorderToRow(headerRow, 6);
1693
+ applyBorderToRow(headerRow, A5_COLUMN_COUNT);
778
1694
  const headerRowNum = headerRow.number;
779
1695
  for (const [i, column] of table.columns.entries()) {
780
- const markers = [];
781
- if (column.isPrimaryKey) markers.push(labels.pkMarker);
782
- if (column.isForeignKey) markers.push(labels.fkMarker);
783
- const notes = [markers.join(", "), column.description?.value ?? ""].filter(Boolean).join(" | ");
784
- const row = sheet.addRow([
785
- column.name,
786
- displayValue(column.comment, labels),
787
- column.type,
788
- column.nullable ? labels.no : labels.yes,
789
- column.defaultValue ?? "-",
790
- notes || "-"
791
- ]);
1696
+ const row = sheet.addRow(
1697
+ columnDefinitionRow(column, labels).map(
1698
+ (value, index) => index === 1 ? displayValue(value, labels) : value
1699
+ )
1700
+ );
792
1701
  if (column.isPrimaryKey) {
793
- shadeRow(row, 6, COLOR.pkBg);
1702
+ shadeRow(row, A5_COLUMN_COUNT, COLOR.pkBg);
794
1703
  row.getCell(1).font = { bold: true };
795
1704
  } else if (column.isForeignKey) {
796
- shadeRow(row, 6, COLOR.fkBg);
1705
+ shadeRow(row, A5_COLUMN_COUNT, COLOR.fkBg);
797
1706
  } else if (i % 2 === 1) {
798
- shadeRow(row, 6, COLOR.altRow);
1707
+ shadeRow(row, A5_COLUMN_COUNT, COLOR.altRow);
799
1708
  }
800
- row.getCell(4).alignment = { horizontal: "center" };
801
- applyBorderToRow(row, 6);
1709
+ row.getCell(5).alignment = { horizontal: "center" };
1710
+ row.getCell(9).alignment = { horizontal: "center" };
1711
+ applyBorderToRow(row, A5_COLUMN_COUNT);
802
1712
  }
803
1713
  sheet.columns = [
1714
+ { width: 22 },
804
1715
  { width: 24 },
805
- { width: 28 },
806
- { width: 18 },
1716
+ { width: 16 },
807
1717
  { width: 10 },
808
- { width: 18 },
809
- { width: 36 }
1718
+ { width: 8 },
1719
+ { width: 14 },
1720
+ { width: 8 },
1721
+ { width: 8 },
1722
+ { width: 8 },
1723
+ { width: 28 }
810
1724
  ];
811
1725
  sheet.views = [{ state: "frozen", ySplit: headerRowNum }];
812
1726
  }
@@ -876,57 +1790,8 @@ function collectTableIndexes(table, doc) {
876
1790
  ];
877
1791
  }
878
1792
 
879
- // src/exporters/diagram/mermaid-exporter.ts
880
- import { mkdir as mkdir2, writeFile } from "fs/promises";
881
- import { join as join2 } from "path";
882
- async function exportMermaidDiagram(doc, options) {
883
- await mkdir2(options.outDir, { recursive: true });
884
- await writeFile(
885
- join2(options.outDir, "er_diagram.mmd"),
886
- renderMermaid(doc),
887
- "utf8"
888
- );
889
- }
890
- function renderMermaid(doc) {
891
- const lines = ["erDiagram"];
892
- for (const warning of doc.warnings) {
893
- const target = warning.target ? ` (${warning.target})` : "";
894
- lines.push(
895
- ` %% WARNING [${warning.severity}] ${warning.code}${target}: ${warning.message}`
896
- );
897
- }
898
- for (const table of doc.tables) {
899
- for (const todo of table.reviewTodos) {
900
- lines.push(` %% TODO [${todo.type}] ${todo.target}: ${todo.issue}`);
901
- }
902
- lines.push(` ${table.name} {`);
903
- for (const column of table.columns) {
904
- const markers = [
905
- column.isPrimaryKey ? "PK" : "",
906
- column.isForeignKey ? "FK" : ""
907
- ].filter(Boolean).join(" ");
908
- lines.push(
909
- ` ${sanitizeType(column.type)} ${column.name}${markers ? ` "${markers}"` : ""}`
910
- );
911
- }
912
- lines.push(" }");
913
- }
914
- for (const relationship of doc.relationships.filter(
915
- (item) => item.source === "schema"
916
- )) {
917
- lines.push(
918
- ` ${relationship.toTable} ||--o{ ${relationship.fromTable} : "${relationship.constraintName ?? relationship.fromColumn}"`
919
- );
920
- }
921
- return `${lines.join("\n")}
922
- `;
923
- }
924
- function sanitizeType(type) {
925
- return type.replace(/[^a-zA-Z0-9_]/g, "_");
926
- }
927
-
928
1793
  // src/exporters/markdown/markdown-exporter.ts
929
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
1794
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
930
1795
  import { join as join3 } from "path";
931
1796
 
932
1797
  // src/core/sanitize.ts
@@ -940,8 +1805,16 @@ async function exportMarkdownDocs(doc, options) {
940
1805
  const tablesDir = join3(options.outDir, "tables");
941
1806
  await mkdir3(tablesDir, { recursive: true });
942
1807
  const labels = getOutputLabels(options.language);
1808
+ if (doc.tables.length > 0) {
1809
+ const mermaid = getErDiagramMermaid(doc);
1810
+ await writeFile3(
1811
+ join3(options.outDir, "ER_DIAGRAM.md"),
1812
+ renderErDiagramMarkdown(mermaid, labels),
1813
+ "utf8"
1814
+ );
1815
+ }
943
1816
  for (const table of doc.tables) {
944
- await writeFile2(
1817
+ await writeFile3(
945
1818
  join3(tablesDir, `${sanitizeFilename(table.name)}.md`),
946
1819
  renderTableDoc(table, doc, labels),
947
1820
  "utf8"
@@ -988,12 +1861,12 @@ function renderTableDoc(table, doc, labels) {
988
1861
  lines.push(`## ${labels.columnsHeading}`);
989
1862
  lines.push("");
990
1863
  lines.push(
991
- `| ${labels.physicalName} | ${labels.logicalName} | ${labels.type} | ${labels.required} | ${labels.defaultValue} | ${labels.notes} |`
1864
+ `| ${columnDefinitionHeaders(labels).join(" | ")} |`
992
1865
  );
993
- lines.push("|--------|--------|----|------|--------------|------|");
1866
+ lines.push(`|${columnDefinitionHeaders(labels).map(() => "--------").join("|")}|`);
994
1867
  for (const col of table.columns) {
995
1868
  lines.push(
996
- `| ${escapeMd(col.name)} | ${escapeMd(col.comment ?? "")} | ${escapeMd(col.type)} | ${col.nullable ? labels.no : labels.yes} | ${escapeMd(col.defaultValue ?? "-")} | ${escapeMd(col.description?.value ?? "")} |`
1869
+ `| ${columnDefinitionRow(col, labels).map((value) => escapeMd(value)).join(" | ")} |`
997
1870
  );
998
1871
  }
999
1872
  lines.push("");
@@ -1012,7 +1885,7 @@ function escapeMd(text) {
1012
1885
  }
1013
1886
 
1014
1887
  // src/exporters/html/html-exporter.ts
1015
- import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
1888
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
1016
1889
  import { join as join4 } from "path";
1017
1890
  async function exportHtmlDocs(doc, options) {
1018
1891
  try {
@@ -1020,13 +1893,21 @@ async function exportHtmlDocs(doc, options) {
1020
1893
  const tablesDir = join4(htmlDir, "tables");
1021
1894
  await mkdir4(tablesDir, { recursive: true });
1022
1895
  const labels = getOutputLabels(options.language);
1023
- await writeFile3(
1896
+ await writeFile4(
1024
1897
  join4(htmlDir, "index.html"),
1025
1898
  renderIndexPage(doc, labels),
1026
1899
  "utf8"
1027
1900
  );
1901
+ if (doc.tables.length > 0) {
1902
+ const mermaid = getErDiagramMermaid(doc);
1903
+ await writeFile4(
1904
+ join4(htmlDir, "er-diagram.html"),
1905
+ renderErDiagramHtmlPage(mermaid, labels),
1906
+ "utf8"
1907
+ );
1908
+ }
1028
1909
  for (const table of doc.tables) {
1029
- await writeFile3(
1910
+ await writeFile4(
1030
1911
  join4(tablesDir, `${sanitizeFilename(table.name)}.html`),
1031
1912
  renderTablePage(table, doc, labels),
1032
1913
  "utf8"
@@ -1108,7 +1989,7 @@ function pageShell(title, body, fromSubdir = false) {
1108
1989
  <head>
1109
1990
  <meta charset="UTF-8">
1110
1991
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1111
- <title>${esc(title)}</title>
1992
+ <title>${esc2(title)}</title>
1112
1993
  <style>${CSS} </style>
1113
1994
  </head>
1114
1995
  <body>
@@ -1125,34 +2006,35 @@ function renderIndexPage(doc, labels) {
1125
2006
  const fkCount = table.foreignKeys.length;
1126
2007
  const fileName = sanitizeFilename(table.name);
1127
2008
  tableRows += ` <tr>
1128
- <td><a href="tables/${fileName}.html">${esc(table.name)}</a></td>
1129
- <td>${esc(table.comment ?? "")}</td>
2009
+ <td><a href="tables/${fileName}.html">${esc2(table.name)}</a></td>
2010
+ <td>${esc2(table.comment ?? "")}</td>
1130
2011
  <td style="text-align:center">${table.columns.length}</td>
1131
- <td>${esc(pkCols)}</td>
2012
+ <td>${esc2(pkCols)}</td>
1132
2013
  <td style="text-align:center">${fkCount}</td>
1133
2014
  </tr>
1134
2015
  `;
1135
2016
  }
1136
2017
  const body = `
1137
- <h1>${esc(labels.docTitle)}</h1>
2018
+ <h1>${esc2(labels.docTitle)}</h1>
1138
2019
  <div class="summary">
1139
- <div class="summary-item"><div class="num">${doc.tables.length}</div><div class="lbl">${esc(labels.tablesLabel)}</div></div>
1140
- <div class="summary-item"><div class="num">${doc.relationships.length}</div><div class="lbl">${esc(labels.relationshipsLabel)}</div></div>
1141
- <div class="summary-item"><div class="num">${doc.dialect}</div><div class="lbl">${esc(labels.dialectLabel)}</div></div>
2020
+ <div class="summary-item"><div class="num">${doc.tables.length}</div><div class="lbl">${esc2(labels.tablesLabel)}</div></div>
2021
+ <div class="summary-item"><div class="num">${doc.relationships.length}</div><div class="lbl">${esc2(labels.relationshipsLabel)}</div></div>
2022
+ <div class="summary-item"><div class="num">${doc.dialect}</div><div class="lbl">${esc2(labels.dialectLabel)}</div></div>
1142
2023
  </div>
1143
- <h2>${esc(labels.tableListHeading)}</h2>
2024
+ <p class="back"><a href="er-diagram.html">${esc2(labels.erDiagramHeading)} \u2192</a></p>
2025
+ <h2>${esc2(labels.tableListHeading)}</h2>
1144
2026
  <table class="table-list">
1145
2027
  <thead><tr>
1146
- <th>${esc(labels.tableLabel)}</th>
1147
- <th>${esc(labels.tableLogicalName)}</th>
2028
+ <th>${esc2(labels.tableLabel)}</th>
2029
+ <th>${esc2(labels.tableLogicalName)}</th>
1148
2030
  <th style="width:70px;text-align:center">Cols</th>
1149
- <th>${esc(labels.primaryKey)}</th>
2031
+ <th>${esc2(labels.primaryKey)}</th>
1150
2032
  <th style="width:50px;text-align:center">FK</th>
1151
2033
  </tr></thead>
1152
2034
  <tbody>
1153
2035
  ${tableRows} </tbody>
1154
2036
  </table>
1155
- <p class="note">${esc(labels.generatedNote)}</p>
2037
+ <p class="note">${esc2(labels.generatedNote)}</p>
1156
2038
  `;
1157
2039
  return pageShell(labels.docTitle, body);
1158
2040
  }
@@ -1170,39 +2052,34 @@ function renderTablePage(table, doc, labels) {
1170
2052
  const pkBadge = col.isPrimaryKey ? `<span class="badge badge-pk">PK</span>` : "";
1171
2053
  const fkBadge = col.isForeignKey ? `<span class="badge badge-fk">FK</span>` : "";
1172
2054
  const rowClass = col.isPrimaryKey ? "pk" : col.isForeignKey ? "fk" : "";
1173
- const required = col.nullable ? labels.no : labels.yes;
1174
- colRows += ` <tr${rowClass ? ` class="${rowClass}"` : ""}><td>${esc(col.name)}${pkBadge}${fkBadge}</td><td>${esc(col.comment ?? "")}</td><td>${esc(col.type)}</td><td>${required}</td><td>${esc(col.defaultValue ?? "-")}</td><td>${esc(col.description?.value ?? "")}</td></tr>
2055
+ const cells = columnDefinitionRow(col, labels);
2056
+ colRows += ` <tr${rowClass ? ` class="${rowClass}"` : ""}><td>${esc2(cells[0] ?? "")}${pkBadge}${fkBadge}</td>` + cells.slice(1).map((cell) => `<td>${esc2(cell)}</td>`).join("") + `</tr>
1175
2057
  `;
1176
2058
  }
1177
2059
  const body = `
1178
- <p class="back"><a href="../index.html">\u2190 ${esc(labels.tableListHeading)}</a></p>
1179
- <h1>${esc(table.name)}</h1>
1180
- <h2>${esc(labels.tableInfoHeading)}</h2>
2060
+ <p class="back"><a href="../index.html">\u2190 ${esc2(labels.tableListHeading)}</a></p>
2061
+ <h1>${esc2(table.name)}</h1>
2062
+ <h2>${esc2(labels.tableInfoHeading)}</h2>
1181
2063
  <table class="meta">
1182
2064
  <tbody>
1183
- <tr><th>${esc(labels.tablePhysicalName)}</th><td>${esc(table.name)}</td></tr>
1184
- <tr><th>${esc(labels.tableLogicalName)}</th><td>${esc(table.comment ?? "")}</td></tr>
1185
- <tr><th>${esc(labels.schema)}</th><td>${esc(table.schema ?? "")}</td></tr>
1186
- <tr><th>${esc(labels.primaryKey)}</th><td>${esc(table.primaryKeys.join(", ") || labels.none)}</td></tr>
1187
- <tr><th>${esc(labels.foreignKeys)}</th><td>${foreignKeys}</td></tr>
1188
- <tr><th>${esc(labels.indexes)}</th><td>${indexText}</td></tr>
2065
+ <tr><th>${esc2(labels.tablePhysicalName)}</th><td>${esc2(table.name)}</td></tr>
2066
+ <tr><th>${esc2(labels.tableLogicalName)}</th><td>${esc2(table.comment ?? "")}</td></tr>
2067
+ <tr><th>${esc2(labels.schema)}</th><td>${esc2(table.schema ?? "")}</td></tr>
2068
+ <tr><th>${esc2(labels.primaryKey)}</th><td>${esc2(table.primaryKeys.join(", ") || labels.none)}</td></tr>
2069
+ <tr><th>${esc2(labels.foreignKeys)}</th><td>${foreignKeys}</td></tr>
2070
+ <tr><th>${esc2(labels.indexes)}</th><td>${indexText}</td></tr>
1189
2071
  </tbody>
1190
2072
  </table>
1191
2073
 
1192
- <h2>${esc(labels.columnsHeading)}</h2>
2074
+ <h2>${esc2(labels.columnsHeading)}</h2>
1193
2075
  <table class="columns">
1194
2076
  <thead><tr>
1195
- <th>${esc(labels.physicalName)}</th>
1196
- <th>${esc(labels.logicalName)}</th>
1197
- <th>${esc(labels.type)}</th>
1198
- <th>${esc(labels.required)}</th>
1199
- <th>${esc(labels.defaultValue)}</th>
1200
- <th>${esc(labels.notes)}</th>
2077
+ ${columnDefinitionHeaders(labels).map((header) => `<th>${esc2(header)}</th>`).join("\n ")}
1201
2078
  </tr></thead>
1202
2079
  <tbody>
1203
2080
  ${colRows} </tbody>
1204
2081
  </table>
1205
- <p class="note">${esc(labels.generatedNote)}</p>
2082
+ <p class="note">${esc2(labels.generatedNote)}</p>
1206
2083
  `;
1207
2084
  return pageShell(table.name, body);
1208
2085
  }
@@ -1214,12 +2091,12 @@ function collectTableIndexes3(table, doc) {
1214
2091
  )
1215
2092
  ];
1216
2093
  }
1217
- function esc(text) {
2094
+ function esc2(text) {
1218
2095
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1219
2096
  }
1220
2097
 
1221
2098
  // src/exporters/word/word-exporter.ts
1222
- import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
2099
+ import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
1223
2100
  import { join as join5 } from "path";
1224
2101
  import {
1225
2102
  Document,
@@ -1229,7 +2106,8 @@ import {
1229
2106
  TableCell,
1230
2107
  TableRow,
1231
2108
  TextRun,
1232
- HeadingLevel
2109
+ HeadingLevel,
2110
+ ImageRun
1233
2111
  } from "docx";
1234
2112
  async function exportWordDocument(doc, options) {
1235
2113
  try {
@@ -1263,6 +2141,53 @@ async function exportWordDocument(doc, options) {
1263
2141
  children: [new TextRun(`${labels.relationshipsLabel}: ${doc.relationships.length}`)]
1264
2142
  })
1265
2143
  );
2144
+ if (doc.tables.length > 0) {
2145
+ children.push(
2146
+ new Paragraph({
2147
+ heading: HeadingLevel.HEADING_2,
2148
+ children: [new TextRun(labels.erDiagramHeading)]
2149
+ })
2150
+ );
2151
+ try {
2152
+ const { buffer: png, width, height } = await renderErDiagramPng(doc);
2153
+ await writeFile5(join5(options.outDir, "er_diagram.png"), png);
2154
+ const fitted = fitErDiagramToBox(width, height, 620, 900);
2155
+ children.push(
2156
+ new Paragraph({
2157
+ children: [
2158
+ new ImageRun({
2159
+ data: png,
2160
+ transformation: fitted,
2161
+ type: "png"
2162
+ })
2163
+ ]
2164
+ })
2165
+ );
2166
+ } catch {
2167
+ children.push(
2168
+ new Paragraph({
2169
+ children: [new TextRun(labels.viewErDiagram)]
2170
+ })
2171
+ );
2172
+ }
2173
+ const mermaid = getErDiagramMermaid(doc);
2174
+ children.push(
2175
+ new Paragraph({
2176
+ children: [new TextRun({ text: "Mermaid source", bold: true })]
2177
+ })
2178
+ );
2179
+ children.push(
2180
+ new Paragraph({
2181
+ children: [
2182
+ new TextRun({
2183
+ text: mermaid,
2184
+ font: "Courier New",
2185
+ size: 18
2186
+ })
2187
+ ]
2188
+ })
2189
+ );
2190
+ }
1266
2191
  children.push(
1267
2192
  new Paragraph({
1268
2193
  heading: HeadingLevel.HEADING_2,
@@ -1463,7 +2388,7 @@ async function exportWordDocument(doc, options) {
1463
2388
  sections: [{ children }]
1464
2389
  });
1465
2390
  const buffer = await Packer.toBuffer(wordDoc);
1466
- await writeFile4(join5(options.outDir, "database_document.docx"), buffer);
2391
+ await writeFile5(join5(options.outDir, "database_document.docx"), buffer);
1467
2392
  } catch (err) {
1468
2393
  throw new Error(
1469
2394
  `Failed to export Word document: ${err instanceof Error ? err.message : String(err)}`,
@@ -1522,14 +2447,7 @@ function renderTableDetail(table, doc, labels) {
1522
2447
  return items;
1523
2448
  }
1524
2449
  function renderColumnsTable(table, labels) {
1525
- const headerCells = [
1526
- labels.physicalName,
1527
- labels.logicalName,
1528
- labels.type,
1529
- labels.required,
1530
- labels.defaultValue,
1531
- labels.notes
1532
- ].map(
2450
+ const headerCells = columnDefinitionHeaders(labels).map(
1533
2451
  (h) => new TableCell({
1534
2452
  children: [
1535
2453
  new Paragraph({ children: [new TextRun({ text: h, bold: true })] })
@@ -1540,40 +2458,11 @@ function renderColumnsTable(table, labels) {
1540
2458
  for (const col of table.columns) {
1541
2459
  colRows.push(
1542
2460
  new TableRow({
1543
- children: [
1544
- new TableCell({
1545
- children: [new Paragraph({ children: [new TextRun(col.name)] })]
1546
- }),
1547
- new TableCell({
1548
- children: [
1549
- new Paragraph({ children: [new TextRun(col.comment ?? "")] })
1550
- ]
1551
- }),
1552
- new TableCell({
1553
- children: [new Paragraph({ children: [new TextRun(col.type)] })]
1554
- }),
1555
- new TableCell({
1556
- children: [
1557
- new Paragraph({
1558
- children: [new TextRun(col.nullable ? labels.no : labels.yes)]
1559
- })
1560
- ]
1561
- }),
1562
- new TableCell({
1563
- children: [
1564
- new Paragraph({
1565
- children: [new TextRun(col.defaultValue ?? "-")]
1566
- })
1567
- ]
1568
- }),
1569
- new TableCell({
1570
- children: [
1571
- new Paragraph({
1572
- children: [new TextRun(col.description?.value ?? "")]
1573
- })
1574
- ]
2461
+ children: columnDefinitionRow(col, labels).map(
2462
+ (value) => new TableCell({
2463
+ children: [new Paragraph({ children: [new TextRun(value)] })]
1575
2464
  })
1576
- ]
2465
+ )
1577
2466
  })
1578
2467
  );
1579
2468
  }