@memberjunction/db-auto-doc 6.2.0-edge.4 β†’ 6.2.0-edge.5

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.
Files changed (112) hide show
  1. package/README.md +95 -0
  2. package/dist/commands/analyze.d.ts +1 -0
  3. package/dist/commands/analyze.d.ts.map +1 -1
  4. package/dist/commands/analyze.js +6 -0
  5. package/dist/commands/analyze.js.map +1 -1
  6. package/dist/commands/export.d.ts +28 -19
  7. package/dist/commands/export.d.ts.map +1 -1
  8. package/dist/commands/export.js +91 -12
  9. package/dist/commands/export.js.map +1 -1
  10. package/dist/core/AnalysisEngine.d.ts +15 -6
  11. package/dist/core/AnalysisEngine.d.ts.map +1 -1
  12. package/dist/core/AnalysisEngine.js +41 -40
  13. package/dist/core/AnalysisEngine.js.map +1 -1
  14. package/dist/core/AnalysisOrchestrator.d.ts +20 -1
  15. package/dist/core/AnalysisOrchestrator.d.ts.map +1 -1
  16. package/dist/core/AnalysisOrchestrator.js +131 -27
  17. package/dist/core/AnalysisOrchestrator.js.map +1 -1
  18. package/dist/core/PKProposalGate.d.ts +22 -0
  19. package/dist/core/PKProposalGate.d.ts.map +1 -0
  20. package/dist/core/PKProposalGate.js +52 -0
  21. package/dist/core/PKProposalGate.js.map +1 -0
  22. package/dist/database/Database.d.ts +8 -0
  23. package/dist/database/Database.d.ts.map +1 -1
  24. package/dist/database/Database.js +23 -1
  25. package/dist/database/Database.js.map +1 -1
  26. package/dist/discovery/EnumCandidateGate.d.ts +19 -0
  27. package/dist/discovery/EnumCandidateGate.d.ts.map +1 -1
  28. package/dist/discovery/EnumCandidateGate.js +47 -5
  29. package/dist/discovery/EnumCandidateGate.js.map +1 -1
  30. package/dist/discovery/FKGraphWalker.d.ts +35 -0
  31. package/dist/discovery/FKGraphWalker.d.ts.map +1 -1
  32. package/dist/discovery/FKGraphWalker.js +113 -12
  33. package/dist/discovery/FKGraphWalker.js.map +1 -1
  34. package/dist/discovery/LLMDiscoveryValidator.d.ts +2 -0
  35. package/dist/discovery/LLMDiscoveryValidator.d.ts.map +1 -1
  36. package/dist/discovery/LLMDiscoveryValidator.js +20 -9
  37. package/dist/discovery/LLMDiscoveryValidator.js.map +1 -1
  38. package/dist/discovery/OrganicKeyDetector.d.ts.map +1 -1
  39. package/dist/discovery/OrganicKeyDetector.js +8 -2
  40. package/dist/discovery/OrganicKeyDetector.js.map +1 -1
  41. package/dist/discovery/StructuralPhase.d.ts +27 -5
  42. package/dist/discovery/StructuralPhase.d.ts.map +1 -1
  43. package/dist/discovery/StructuralPhase.js +42 -9
  44. package/dist/discovery/StructuralPhase.js.map +1 -1
  45. package/dist/discovery/TransitiveBridgeDetector.d.ts +5 -1
  46. package/dist/discovery/TransitiveBridgeDetector.d.ts.map +1 -1
  47. package/dist/discovery/TransitiveBridgeDetector.js +25 -3
  48. package/dist/discovery/TransitiveBridgeDetector.js.map +1 -1
  49. package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -1
  50. package/dist/drivers/BaseAutoDocDriver.js +3 -5
  51. package/dist/drivers/BaseAutoDocDriver.js.map +1 -1
  52. package/dist/drivers/PostgreSQLDriver.d.ts +9 -1
  53. package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -1
  54. package/dist/drivers/PostgreSQLDriver.js +41 -19
  55. package/dist/drivers/PostgreSQLDriver.js.map +1 -1
  56. package/dist/drivers/SQLServerDriver.d.ts.map +1 -1
  57. package/dist/drivers/SQLServerDriver.js +2 -1
  58. package/dist/drivers/SQLServerDriver.js.map +1 -1
  59. package/dist/generators/AdditionalSchemaInfoGenerator.d.ts +34 -0
  60. package/dist/generators/AdditionalSchemaInfoGenerator.d.ts.map +1 -1
  61. package/dist/generators/AdditionalSchemaInfoGenerator.js +85 -4
  62. package/dist/generators/AdditionalSchemaInfoGenerator.js.map +1 -1
  63. package/dist/generators/ErdClusterGenerator.d.ts +44 -0
  64. package/dist/generators/ErdClusterGenerator.d.ts.map +1 -0
  65. package/dist/generators/ErdClusterGenerator.js +216 -0
  66. package/dist/generators/ErdClusterGenerator.js.map +1 -0
  67. package/dist/generators/ErdClusterer.d.ts +130 -0
  68. package/dist/generators/ErdClusterer.d.ts.map +1 -0
  69. package/dist/generators/ErdClusterer.js +308 -0
  70. package/dist/generators/ErdClusterer.js.map +1 -0
  71. package/dist/generators/HTMLGenerator.d.ts +6 -0
  72. package/dist/generators/HTMLGenerator.d.ts.map +1 -1
  73. package/dist/generators/HTMLGenerator.js +41 -6
  74. package/dist/generators/HTMLGenerator.js.map +1 -1
  75. package/dist/generators/MermaidGenerator.d.ts +3 -0
  76. package/dist/generators/MermaidGenerator.d.ts.map +1 -1
  77. package/dist/generators/MermaidGenerator.js +4 -0
  78. package/dist/generators/MermaidGenerator.js.map +1 -1
  79. package/dist/generators/index.d.ts +2 -0
  80. package/dist/generators/index.d.ts.map +1 -1
  81. package/dist/generators/index.js +2 -0
  82. package/dist/generators/index.js.map +1 -1
  83. package/dist/types/analysis.d.ts +2 -0
  84. package/dist/types/analysis.d.ts.map +1 -1
  85. package/dist/types/config.d.ts +46 -0
  86. package/dist/types/config.d.ts.map +1 -1
  87. package/dist/types/config.js +7 -1
  88. package/dist/types/config.js.map +1 -1
  89. package/dist/types/discovery.d.ts +2 -0
  90. package/dist/types/discovery.d.ts.map +1 -1
  91. package/dist/types/state.d.ts +27 -0
  92. package/dist/types/state.d.ts.map +1 -1
  93. package/dist/utils/config-loader.d.ts +5 -0
  94. package/dist/utils/config-loader.d.ts.map +1 -1
  95. package/dist/utils/config-loader.js +37 -0
  96. package/dist/utils/config-loader.js.map +1 -1
  97. package/dist/utils/index-hints.d.ts +36 -0
  98. package/dist/utils/index-hints.d.ts.map +1 -0
  99. package/dist/utils/index-hints.js +65 -0
  100. package/dist/utils/index-hints.js.map +1 -0
  101. package/dist/utils/index.d.ts +1 -0
  102. package/dist/utils/index.d.ts.map +1 -1
  103. package/dist/utils/index.js +1 -0
  104. package/dist/utils/index.js.map +1 -1
  105. package/dist/utils/llm-factory.d.ts +1 -0
  106. package/dist/utils/llm-factory.d.ts.map +1 -1
  107. package/dist/utils/llm-factory.js +6 -1
  108. package/dist/utils/llm-factory.js.map +1 -1
  109. package/package.json +5 -5
  110. package/prompts/fk-pruning-table.md +13 -1
  111. package/prompts/pk-pruning-table.md +10 -0
  112. package/prompts/table-analysis.md +11 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Splits a database's foreign-key graph into hub-centred clusters ("subject areas") small enough
3
+ * to draw one at a time. Pure computation over the analysis state; rendering lives in
4
+ * ErdClusterGenerator.
5
+ *
6
+ * Method:
7
+ * 1. Reference (lookup) tables β€” tables that reference nothing and hold few rows β€” are set aside.
8
+ * Left in, a table such as a yes/no flag referenced by sixty others links every subject area to
9
+ * every other and the clusters collapse into one.
10
+ * 2. Hubs are the tables referenced by at least `hubMinInbound` non-reference tables, plus any
11
+ * names in `hubs`.
12
+ * 3. Every other connected table is assigned to its nearest hub (multi-source breadth-first search
13
+ * over the undirected FK graph).
14
+ * 4. A hub whose cluster is smaller than `minClusterSize` is folded into the neighbouring cluster
15
+ * its members link to most, so the index is not littered with two-table "areas".
16
+ * 5. A cluster larger than `pageSize` is paginated; the hub appears on every page.
17
+ *
18
+ * Tables are keyed `schema.table` with case preserved: same-named tables in different schemas are
19
+ * different tables, and so are `Customer` and `customer` on PostgreSQL.
20
+ */
21
+ import { DatabaseDocumentation, TableDefinition } from '../types/state.js';
22
+ /** Mirrors the `output.erd` keys of config.json (camelCase) so a config object can be passed straight through. */
23
+ export interface ErdClustererOptions {
24
+ hubMinInbound?: number;
25
+ lookupMaxRows?: number;
26
+ pageSize?: number;
27
+ minClusterSize?: number;
28
+ hubs?: string[];
29
+ excludeTables?: string[];
30
+ lookupTables?: string[];
31
+ }
32
+ /** One foreign key edge: `From.FromColumn` references `To.ToColumn`. Keys are `schema.table`. */
33
+ export interface ErdEdge {
34
+ From: string;
35
+ To: string;
36
+ FromColumn: string;
37
+ ToColumn: string;
38
+ }
39
+ export interface ErdClusterPage {
40
+ /** 1-based page number within the cluster. */
41
+ Number: number;
42
+ /** `schema.table` keys on this page; the hub is always first. */
43
+ Tables: string[];
44
+ /** File name (without extension) every renderer uses for this page, unique across the plan. */
45
+ BaseName: string;
46
+ /** Page title, e.g. `Customer (2 of 5)`. */
47
+ Title: string;
48
+ }
49
+ export interface ErdCluster {
50
+ /** `schema.table` key of the hub table. */
51
+ Hub: string;
52
+ /** Display name of the hub table. */
53
+ HubName: string;
54
+ /** `schema.table` keys of every table in the cluster, hub first. */
55
+ Tables: string[];
56
+ Pages: ErdClusterPage[];
57
+ /** FK edges with both ends inside the cluster. */
58
+ InternalEdges: ErdEdge[];
59
+ /** FK edges leaving the cluster (From inside, To in another cluster). */
60
+ OutboundEdges: ErdEdge[];
61
+ /** FK edges entering the cluster (To inside, From in another cluster). */
62
+ InboundEdges: ErdEdge[];
63
+ /** Reference tables used by members of this cluster, with how many members use each. */
64
+ ReferenceTables: Array<{
65
+ Table: string;
66
+ UsedBy: number;
67
+ }>;
68
+ }
69
+ export type ErdClustererSettings = Required<Pick<ErdClustererOptions, 'hubMinInbound' | 'lookupMaxRows' | 'pageSize' | 'minClusterSize'>>;
70
+ export interface ErdClusterPlan {
71
+ Clusters: ErdCluster[];
72
+ /** Settings that named tables the plan could not use (an unknown or edge-less hub, an unknown exclusion). */
73
+ Warnings: string[];
74
+ /** Reference tables set aside before clustering, with how many tables reference each. */
75
+ ReferenceTables: Array<{
76
+ Table: string;
77
+ ReferencedBy: number;
78
+ }>;
79
+ /** Tables with no foreign keys in either direction. */
80
+ IsolatedTables: string[];
81
+ /** Connected tables that reached no hub (small chains with no hub of their own). */
82
+ UnassignedTables: string[];
83
+ /** Tables removed by `excludeTables`. */
84
+ ExcludedTables: string[];
85
+ TotalTables: number;
86
+ TotalEdges: number;
87
+ Options: ErdClustererSettings;
88
+ /** Display name for a table key: the bare table name, or `schema.table` when another schema has a table of that name. */
89
+ NameOf(key: string): string;
90
+ /** The table definition for a key. */
91
+ TableOf(key: string): TableDefinition;
92
+ }
93
+ export declare class ErdClusterer {
94
+ Plan(state: DatabaseDocumentation, options?: ErdClustererOptions): ErdClusterPlan;
95
+ /** Defaults fill the gaps; every count must be at least 1 or the pagination and hub selection degenerate. */
96
+ private resolveSettings;
97
+ /** Index the tables, resolve exclusions and reference tables, and collect the FK graph. */
98
+ private buildGraph;
99
+ /** Distinct FK edges between known tables (a table may reference another through several columns). */
100
+ private collectEdges;
101
+ /**
102
+ * Resolve an FK target to a key. `dep.table` is usually the bare name within `dep.schema`, which also
103
+ * covers a table name that contains a dot (`Customer.bak`); the LLM sometimes writes `schema.table`
104
+ * instead, which the second lookup accepts.
105
+ */
106
+ private resolveTarget;
107
+ /** A configured table name may be given as `schema.table` or as a bare name (any schema, case-insensitive). */
108
+ private resolveNamed;
109
+ private isReferenceTable;
110
+ private link;
111
+ private neighbours;
112
+ private selectHubs;
113
+ /** Multi-source breadth-first search: every reachable table belongs to the hub nearest to it. */
114
+ private assignToNearestHub;
115
+ /** Fold each cluster below the minimum size into the neighbouring cluster its members link to most. */
116
+ private foldUndersizedClusters;
117
+ /**
118
+ * Folding only moves a small cluster into a larger neighbour, so two undersized clusters that
119
+ * link only to each other stay as they are. Say so rather than silently ignoring minClusterSize.
120
+ */
121
+ private warnAboutUndersizedClusters;
122
+ private buildCluster;
123
+ /**
124
+ * File names are decided once here so every renderer links to the same page. Two hubs whose names
125
+ * sanitise to the same identifier (`Order-Item`, `Order_Item`) get a numeric suffix instead of
126
+ * overwriting each other.
127
+ */
128
+ private assignPageFileNames;
129
+ }
130
+ //# sourceMappingURL=ErdClusterer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErdClusterer.d.ts","sourceRoot":"","sources":["../../src/generators/ErdClusterer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Splits a database's foreign-key graph into hub-centred clusters ("subject areas") small enough
3
+ * to draw one at a time. Pure computation over the analysis state; rendering lives in
4
+ * ErdClusterGenerator.
5
+ *
6
+ * Method:
7
+ * 1. Reference (lookup) tables β€” tables that reference nothing and hold few rows β€” are set aside.
8
+ * Left in, a table such as a yes/no flag referenced by sixty others links every subject area to
9
+ * every other and the clusters collapse into one.
10
+ * 2. Hubs are the tables referenced by at least `hubMinInbound` non-reference tables, plus any
11
+ * names in `hubs`.
12
+ * 3. Every other connected table is assigned to its nearest hub (multi-source breadth-first search
13
+ * over the undirected FK graph).
14
+ * 4. A hub whose cluster is smaller than `minClusterSize` is folded into the neighbouring cluster
15
+ * its members link to most, so the index is not littered with two-table "areas".
16
+ * 5. A cluster larger than `pageSize` is paginated; the hub appears on every page.
17
+ *
18
+ * Tables are keyed `schema.table` with case preserved: same-named tables in different schemas are
19
+ * different tables, and so are `Customer` and `customer` on PostgreSQL.
20
+ */
21
+ import { ERD_DEFAULTS } from '../types/config.js';
22
+ /** The fold pass converges in one or two rounds; this caps a pathological ping-pong between two tiny areas. */
23
+ const MAX_FOLD_PASSES = 5;
24
+ export class ErdClusterer {
25
+ Plan(state, options = {}) {
26
+ const settings = this.resolveSettings(options);
27
+ const graph = this.buildGraph(state, options, settings);
28
+ const forcedHubs = graph.forcedHubs;
29
+ const connected = [...graph.tables.keys()].filter(k => !graph.references.has(k) && this.neighbours(graph, k).size > 0);
30
+ const isolated = [...graph.tables.keys()].filter(k => !graph.references.has(k) && this.neighbours(graph, k).size === 0);
31
+ const hubs = this.selectHubs(graph, connected, forcedHubs, settings);
32
+ let owner = this.assignToNearestHub(graph, hubs);
33
+ owner = this.foldUndersizedClusters(graph, owner, forcedHubs, settings);
34
+ const members = new Map();
35
+ for (const [table, hub] of owner) {
36
+ if (!members.has(hub))
37
+ members.set(hub, []);
38
+ members.get(hub).push(table);
39
+ }
40
+ this.warnAboutUndersizedClusters(graph, members, forcedHubs, settings);
41
+ const clusters = [...members].map(([hub, list]) => this.buildCluster(graph, owner, hub, list, settings));
42
+ clusters.sort((a, b) => b.Tables.length - a.Tables.length || a.HubName.localeCompare(b.HubName));
43
+ this.assignPageFileNames(clusters);
44
+ return {
45
+ Clusters: clusters,
46
+ Warnings: graph.warnings,
47
+ ReferenceTables: [...graph.references]
48
+ .map(k => ({ Table: graph.nameOf(k), ReferencedBy: graph.inbound.get(k) ?? 0 }))
49
+ .sort((a, b) => b.ReferencedBy - a.ReferencedBy || a.Table.localeCompare(b.Table)),
50
+ IsolatedTables: isolated.map(graph.nameOf).sort(),
51
+ UnassignedTables: connected.filter(k => !owner.has(k)).map(graph.nameOf).sort(),
52
+ ExcludedTables: [...graph.excludedNames].sort(),
53
+ TotalTables: graph.tables.size,
54
+ TotalEdges: graph.edges.size,
55
+ Options: settings,
56
+ NameOf: graph.nameOf,
57
+ TableOf: (key) => graph.tables.get(key),
58
+ };
59
+ }
60
+ /** Defaults fill the gaps; every count must be at least 1 or the pagination and hub selection degenerate. */
61
+ resolveSettings(options) {
62
+ const atLeastOne = (name, value) => {
63
+ const resolved = value ?? ERD_DEFAULTS[name];
64
+ if (!Number.isInteger(resolved) || resolved < 1) {
65
+ throw new Error(`ERD setting ${name} must be a whole number of at least 1 (got ${String(value)})`);
66
+ }
67
+ return resolved;
68
+ };
69
+ return {
70
+ hubMinInbound: atLeastOne('hubMinInbound', options.hubMinInbound),
71
+ lookupMaxRows: atLeastOne('lookupMaxRows', options.lookupMaxRows),
72
+ pageSize: atLeastOne('pageSize', options.pageSize),
73
+ minClusterSize: atLeastOne('minClusterSize', options.minClusterSize),
74
+ };
75
+ }
76
+ /** Index the tables, resolve exclusions and reference tables, and collect the FK graph. */
77
+ buildGraph(state, options, settings) {
78
+ const tables = new Map();
79
+ const schemaOf = new Map();
80
+ for (const schema of state.schemas) {
81
+ for (const table of schema.tables) {
82
+ tables.set(`${schema.name}.${table.name}`, table);
83
+ schemaOf.set(`${schema.name}.${table.name}`, schema.name);
84
+ }
85
+ }
86
+ // A table is shown with its schema only when another schema has a table of the same name.
87
+ const nameCounts = new Map();
88
+ for (const t of tables.values())
89
+ nameCounts.set(t.name, (nameCounts.get(t.name) ?? 0) + 1);
90
+ const nameOf = (key) => {
91
+ const table = tables.get(key);
92
+ if (!table)
93
+ return key;
94
+ return (nameCounts.get(table.name) ?? 0) > 1 ? `${schemaOf.get(key)}.${table.name}` : table.name;
95
+ };
96
+ const graph = {
97
+ tables, schemaOf, nameOf, warnings: [], excludedNames: [], forcedHubs: new Set(),
98
+ edges: new Map(), inbound: new Map(), outbound: new Map(), adjacency: new Map(), hubScore: new Map(), references: new Set(),
99
+ };
100
+ const excluded = this.resolveNamed(graph, options.excludeTables, 'excludeTables');
101
+ for (const key of excluded) {
102
+ graph.excludedNames.push(nameOf(key));
103
+ tables.delete(key);
104
+ }
105
+ this.collectEdges(graph);
106
+ graph.forcedHubs = this.resolveNamed(graph, options.hubs, 'hubs');
107
+ const forcedLookups = this.resolveNamed(graph, options.lookupTables, 'lookupTables');
108
+ for (const key of tables.keys()) {
109
+ if (this.isReferenceTable(graph, key, graph.forcedHubs, forcedLookups, settings))
110
+ graph.references.add(key);
111
+ }
112
+ for (const edge of graph.edges.values()) {
113
+ if (graph.references.has(edge.From) || graph.references.has(edge.To))
114
+ continue;
115
+ this.link(graph, edge.From, edge.To);
116
+ this.link(graph, edge.To, edge.From);
117
+ graph.hubScore.set(edge.To, (graph.hubScore.get(edge.To) ?? 0) + 1);
118
+ }
119
+ return graph;
120
+ }
121
+ /** Distinct FK edges between known tables (a table may reference another through several columns). */
122
+ collectEdges(graph) {
123
+ for (const [from, table] of graph.tables) {
124
+ for (const dep of table.dependsOn ?? []) {
125
+ const to = this.resolveTarget(graph, dep.schema ?? graph.schemaOf.get(from), dep.table ?? '');
126
+ if (!to || to === from)
127
+ continue;
128
+ const id = `${from}->${to}`;
129
+ if (graph.edges.has(id))
130
+ continue;
131
+ graph.edges.set(id, { From: from, To: to, FromColumn: dep.column, ToColumn: dep.referencedColumn });
132
+ graph.outbound.set(from, (graph.outbound.get(from) ?? 0) + 1);
133
+ graph.inbound.set(to, (graph.inbound.get(to) ?? 0) + 1);
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * Resolve an FK target to a key. `dep.table` is usually the bare name within `dep.schema`, which also
139
+ * covers a table name that contains a dot (`Customer.bak`); the LLM sometimes writes `schema.table`
140
+ * instead, which the second lookup accepts.
141
+ */
142
+ resolveTarget(graph, schema, name) {
143
+ const inSchema = `${schema}.${name}`;
144
+ if (graph.tables.has(inSchema))
145
+ return inSchema;
146
+ if (graph.tables.has(name))
147
+ return name;
148
+ return undefined;
149
+ }
150
+ /** A configured table name may be given as `schema.table` or as a bare name (any schema, case-insensitive). */
151
+ resolveNamed(graph, names, setting) {
152
+ const keys = new Set();
153
+ for (const name of names ?? []) {
154
+ const wanted = name.toLowerCase();
155
+ const matches = graph.tables.has(name)
156
+ ? [name]
157
+ : [...graph.tables.keys()].filter(k => k.toLowerCase() === wanted || graph.tables.get(k).name.toLowerCase() === wanted);
158
+ if (matches.length === 0)
159
+ graph.warnings.push(`${setting}: no table named '${name}'`);
160
+ for (const k of matches)
161
+ keys.add(k);
162
+ }
163
+ return keys;
164
+ }
165
+ isReferenceTable(graph, key, forcedHubs, forcedLookups, settings) {
166
+ if (forcedHubs.has(key))
167
+ return false;
168
+ if (forcedLookups.has(key))
169
+ return true;
170
+ return (graph.outbound.get(key) ?? 0) === 0
171
+ && (graph.inbound.get(key) ?? 0) >= 1
172
+ && (graph.tables.get(key).rowCount ?? 0) < settings.lookupMaxRows;
173
+ }
174
+ link(graph, a, b) {
175
+ if (!graph.adjacency.has(a))
176
+ graph.adjacency.set(a, new Set());
177
+ graph.adjacency.get(a).add(b);
178
+ }
179
+ neighbours(graph, key) {
180
+ return graph.adjacency.get(key) ?? new Set();
181
+ }
182
+ selectHubs(graph, connected, forcedHubs, settings) {
183
+ for (const hub of forcedHubs) {
184
+ if (!connected.includes(hub)) {
185
+ graph.warnings.push(`hubs: '${graph.nameOf(hub)}' has no foreign-key relationships to other drawn tables and cannot anchor a subject area`);
186
+ }
187
+ }
188
+ const score = (key) => graph.hubScore.get(key) ?? 0;
189
+ return connected
190
+ .filter(k => forcedHubs.has(k) || score(k) >= settings.hubMinInbound)
191
+ .sort((a, b) => score(b) - score(a));
192
+ }
193
+ /** Multi-source breadth-first search: every reachable table belongs to the hub nearest to it. */
194
+ assignToNearestHub(graph, hubs) {
195
+ const owner = new Map();
196
+ const queue = [];
197
+ for (const hub of hubs) {
198
+ owner.set(hub, hub);
199
+ queue.push(hub);
200
+ }
201
+ for (let head = 0; head < queue.length; head++) {
202
+ const current = queue[head];
203
+ for (const next of this.neighbours(graph, current)) {
204
+ if (!owner.has(next)) {
205
+ owner.set(next, owner.get(current));
206
+ queue.push(next);
207
+ }
208
+ }
209
+ }
210
+ return owner;
211
+ }
212
+ /** Fold each cluster below the minimum size into the neighbouring cluster its members link to most. */
213
+ foldUndersizedClusters(graph, owner, forcedHubs, settings) {
214
+ for (let pass = 0; pass < MAX_FOLD_PASSES; pass++) {
215
+ const sizes = new Map();
216
+ for (const hub of owner.values())
217
+ sizes.set(hub, (sizes.get(hub) ?? 0) + 1);
218
+ const tiny = new Set([...sizes].filter(([hub, n]) => n < settings.minClusterSize && !forcedHubs.has(hub)).map(([hub]) => hub));
219
+ if (tiny.size === 0)
220
+ break;
221
+ for (const hub of tiny) {
222
+ const votes = new Map();
223
+ for (const [table, o] of owner) {
224
+ if (o !== hub)
225
+ continue;
226
+ for (const n of this.neighbours(graph, table)) {
227
+ const target = owner.get(n);
228
+ if (target && target !== hub && !tiny.has(target))
229
+ votes.set(target, (votes.get(target) ?? 0) + 1);
230
+ }
231
+ }
232
+ const best = [...votes].sort((a, b) => b[1] - a[1])[0];
233
+ if (!best)
234
+ continue;
235
+ for (const [table, o] of owner)
236
+ if (o === hub)
237
+ owner.set(table, best[0]);
238
+ }
239
+ }
240
+ return owner;
241
+ }
242
+ /**
243
+ * Folding only moves a small cluster into a larger neighbour, so two undersized clusters that
244
+ * link only to each other stay as they are. Say so rather than silently ignoring minClusterSize.
245
+ */
246
+ warnAboutUndersizedClusters(graph, members, forcedHubs, settings) {
247
+ const undersized = [...members]
248
+ .filter(([hub, list]) => list.length < settings.minClusterSize && !forcedHubs.has(hub))
249
+ .sort((a, b) => graph.nameOf(a[0]).localeCompare(graph.nameOf(b[0])));
250
+ for (const [hub, list] of undersized) {
251
+ graph.warnings.push(`minClusterSize: subject area '${graph.nameOf(hub)}' has ${list.length} table(s), below the minimum of ${settings.minClusterSize}, and no larger neighbouring area to fold into`);
252
+ }
253
+ }
254
+ buildCluster(graph, owner, hub, list, settings) {
255
+ const score = (key) => graph.hubScore.get(key) ?? 0;
256
+ const rest = list.filter(k => k !== hub).sort((a, b) => (score(b) - score(a)) || a.localeCompare(b));
257
+ const ordered = [hub, ...rest];
258
+ const inside = new Set(ordered);
259
+ const pages = [];
260
+ for (let i = 0, n = 1; i < Math.max(1, rest.length); i += settings.pageSize, n++) {
261
+ pages.push({ Number: n, Tables: [hub, ...rest.slice(i, i + settings.pageSize)], BaseName: '', Title: '' });
262
+ }
263
+ const internal = [], outboundEdges = [], inboundEdges = [];
264
+ const referenceUse = new Map();
265
+ for (const e of graph.edges.values()) {
266
+ if (inside.has(e.From) && inside.has(e.To))
267
+ internal.push(e);
268
+ else if (inside.has(e.From) && graph.references.has(e.To))
269
+ referenceUse.set(e.To, (referenceUse.get(e.To) ?? 0) + 1);
270
+ else if (inside.has(e.From) && owner.has(e.To))
271
+ outboundEdges.push(e);
272
+ else if (inside.has(e.To) && owner.has(e.From))
273
+ inboundEdges.push(e);
274
+ }
275
+ return {
276
+ Hub: hub,
277
+ HubName: graph.nameOf(hub),
278
+ Tables: ordered,
279
+ Pages: pages,
280
+ InternalEdges: internal,
281
+ OutboundEdges: outboundEdges,
282
+ InboundEdges: inboundEdges,
283
+ ReferenceTables: [...referenceUse]
284
+ .map(([t, n]) => ({ Table: graph.nameOf(t), UsedBy: n }))
285
+ .sort((a, b) => b.UsedBy - a.UsedBy || a.Table.localeCompare(b.Table)),
286
+ };
287
+ }
288
+ /**
289
+ * File names are decided once here so every renderer links to the same page. Two hubs whose names
290
+ * sanitise to the same identifier (`Order-Item`, `Order_Item`) get a numeric suffix instead of
291
+ * overwriting each other.
292
+ */
293
+ assignPageFileNames(clusters) {
294
+ const taken = new Map();
295
+ for (const cluster of clusters) {
296
+ const base = `cluster-${cluster.HubName.replace(/[^A-Za-z0-9_]/g, '_')}`;
297
+ const seen = taken.get(base) ?? 0;
298
+ taken.set(base, seen + 1);
299
+ const unique = seen === 0 ? base : `${base}~${seen + 1}`;
300
+ const multi = cluster.Pages.length > 1;
301
+ for (const page of cluster.Pages) {
302
+ page.BaseName = multi ? `${unique}-${page.Number}` : unique;
303
+ page.Title = multi ? `${cluster.HubName} (${page.Number} of ${cluster.Pages.length})` : cluster.HubName;
304
+ }
305
+ }
306
+ }
307
+ }
308
+ //# sourceMappingURL=ErdClusterer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErdClusterer.js","sourceRoot":"","sources":["../../src/generators/ErdClusterer.ts"],"names":[],"mappings":"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"}
@@ -2,10 +2,15 @@
2
2
  * Generates interactive HTML documentation with Mermaid ERD
3
3
  */
4
4
  import { DatabaseDocumentation } from '../types/state.js';
5
+ import { ErdClusterPlan } from './ErdClusterer.js';
5
6
  export interface HTMLGeneratorOptions {
6
7
  approvedOnly?: boolean;
7
8
  confidenceThreshold?: number;
8
9
  includeMermaid?: boolean;
10
+ /** When set, a "Subject areas" section links to the per-cluster diagrams and the inline per-schema ERD is replaced by a link to them. */
11
+ clusterPlan?: ErdClusterPlan;
12
+ /** Directory of the cluster pages, relative to documentation.html. Default `erd`. */
13
+ clusterDir?: string;
9
14
  }
10
15
  export declare class HTMLGenerator {
11
16
  /**
@@ -23,6 +28,7 @@ export declare class HTMLGenerator {
23
28
  private appendSearchableList;
24
29
  private appendSearchScript;
25
30
  private appendSchemas;
31
+ private appendSubjectAreas;
26
32
  private generateMermaidERD;
27
33
  private toAnchor;
28
34
  private escapeHtml;
@@ -1 +1 @@
1
- {"version":3,"file":"HTMLGenerator.d.ts","sourceRoot":"","sources":["../../src/generators/HTMLGenerator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,aAAa;IACxB;;OAEG;IACI,QAAQ,CACb,KAAK,EAAE,qBAAqB,EAC5B,OAAO,GAAE,oBAAyB,GACjC,MAAM;IAyCT,wCAAwC;IACjC,QAAQ,CACb,KAAK,EAAE,qBAAqB,EAC5B,OAAO,GAAE,oBAAyB,GACjC,MAAM;IAIT,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,YAAY;IAwWpB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,qBAAqB;IA2C7B,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,oBAAoB;IAoD5B,OAAO,CAAC,kBAAkB;IAwC1B,OAAO,CAAC,aAAa;IAsHrB,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,UAAU;CAGnB"}
1
+ {"version":3,"file":"HTMLGenerator.d.ts","sourceRoot":"","sources":["../../src/generators/HTMLGenerator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKnD,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yIAAyI;IACzI,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,aAAa;IACxB;;OAEG;IACI,QAAQ,CACb,KAAK,EAAE,qBAAqB,EAC5B,OAAO,GAAE,oBAAyB,GACjC,MAAM;IA8CT,wCAAwC;IACjC,QAAQ,CACb,KAAK,EAAE,qBAAqB,EAC5B,OAAO,GAAE,oBAAyB,GACjC,MAAM;IAIT,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,YAAY;IAwWpB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,qBAAqB;IA2C7B,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,oBAAoB;IAoD5B,OAAO,CAAC,kBAAkB;IAwC1B,OAAO,CAAC,aAAa;IA2HrB,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,UAAU;CAGnB"}
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Generates interactive HTML documentation with Mermaid ERD
3
3
  */
4
+ /** Hub descriptions are long; the Subject Areas table shows this much of each. */
5
+ const SUBJECT_AREA_DESCRIPTION_CHARS = 200;
4
6
  export class HTMLGenerator {
5
7
  /**
6
8
  * Generate interactive HTML documentation
@@ -16,13 +18,17 @@ export class HTMLGenerator {
16
18
  this.appendHeaderSection(htmlLines, state);
17
19
  // Sidebar with navigation
18
20
  htmlLines.push('<div class="main-wrapper">');
19
- this.appendSidebar(htmlLines, state);
21
+ this.appendSidebar(htmlLines, state, options);
20
22
  // Main content
21
23
  htmlLines.push('<div class="main-content">');
22
24
  // Analysis summary
23
25
  this.appendAnalysisSummary(htmlLines, state);
24
26
  // Database context
25
27
  this.appendDatabaseContext(htmlLines, state);
28
+ // Subject areas (clustered ERDs), when export produced them
29
+ if (options.clusterPlan) {
30
+ this.appendSubjectAreas(htmlLines, options.clusterPlan, options.clusterDir ?? 'erd');
31
+ }
26
32
  // Searchable table/column list
27
33
  this.appendSearchableList(htmlLines, state, options);
28
34
  // Schemas with ERD and tables
@@ -417,8 +423,12 @@ export class HTMLGenerator {
417
423
  lines.push('</div>');
418
424
  lines.push('</header>');
419
425
  }
420
- appendSidebar(lines, state) {
426
+ appendSidebar(lines, state, options = {}) {
421
427
  lines.push('<aside class="sidebar">');
428
+ if (options.clusterPlan) {
429
+ lines.push('<h3>πŸ—ΊοΈ Subject Areas</h3>');
430
+ lines.push('<a href="#subject-areas">Overview</a>');
431
+ }
422
432
  lines.push('<h3>πŸ“‘ Schemas</h3>');
423
433
  for (const schema of state.schemas) {
424
434
  lines.push(`<a href="#schema-${this.toAnchor(schema.name)}">${this.escapeHtml(schema.name)}</a>`);
@@ -577,11 +587,17 @@ export class HTMLGenerator {
577
587
  if (schema.businessDomains && schema.businessDomains.length > 0) {
578
588
  lines.push(`<p><strong>Business Domains:</strong> ${schema.businessDomains.map(d => this.escapeHtml(d)).join(', ')}</p>`);
579
589
  }
580
- // Entity Relationship Diagram
590
+ // Entity Relationship Diagram. With a cluster plan the schema-wide diagram is replaced by the
591
+ // subject-area pages: one diagram of every table exceeds Mermaid's text limit on large schemas.
581
592
  lines.push('<h3>Entity Relationship Diagram</h3>');
582
- lines.push('<div class="mermaid">');
583
- lines.push(this.generateMermaidERD(schema));
584
- lines.push('</div>');
593
+ if (options.clusterPlan) {
594
+ lines.push(`<p>Drawn per subject area β€” see <a href="#subject-areas">Subject Areas</a> or open <a href="${this.escapeHtml(options.clusterDir ?? 'erd')}/index.html">the subject-area index</a>.</p>`);
595
+ }
596
+ else {
597
+ lines.push('<div class="mermaid">');
598
+ lines.push(this.generateMermaidERD(schema));
599
+ lines.push('</div>');
600
+ }
585
601
  // Tables
586
602
  lines.push('<h3>Tables</h3>');
587
603
  for (const table of schema.tables) {
@@ -659,6 +675,25 @@ export class HTMLGenerator {
659
675
  lines.push('<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"><\/script>');
660
676
  lines.push('<script>mermaid.initialize({ startOnLoad: true, theme: "default" }); mermaid.contentLoaded();<\/script>');
661
677
  }
678
+ appendSubjectAreas(lines, plan, dir) {
679
+ const covered = plan.Clusters.reduce((n, c) => n + c.Tables.length, 0);
680
+ lines.push('<section id="subject-areas">');
681
+ lines.push('<h2>πŸ—ΊοΈ Subject Areas</h2>');
682
+ lines.push(`<p>${plan.TotalTables} tables and ${plan.TotalEdges} foreign-key relationships, drawn as ${plan.Clusters.length} subject areas covering ${covered} tables. Each area is centred on a hub table that many others reference. <a href="${this.escapeHtml(dir)}/index.html">Open the subject-area index</a> for reference tables and tables outside every area.</p>`);
683
+ lines.push('<table>');
684
+ lines.push('<thead><tr><th>Subject area</th><th>Tables</th><th>Diagram</th><th>Hub description</th></tr></thead>');
685
+ lines.push('<tbody>');
686
+ for (const cluster of plan.Clusters) {
687
+ const hub = plan.TableOf(cluster.Hub);
688
+ const pages = cluster.Pages.length > 1
689
+ ? cluster.Pages.map(p => `<a href="${this.escapeHtml(`${dir}/${p.BaseName}`)}.html">page ${p.Number}</a>`).join(', ')
690
+ : `<a href="${this.escapeHtml(`${dir}/${cluster.Pages[0].BaseName}`)}.html">diagram</a>`;
691
+ lines.push(`<tr><td><a href="#table-${this.toAnchor(hub.name)}">${this.escapeHtml(cluster.HubName)}</a></td><td>${cluster.Tables.length}</td><td>${pages}</td><td>${this.escapeHtml((hub.description ?? '').slice(0, SUBJECT_AREA_DESCRIPTION_CHARS))}</td></tr>`);
692
+ }
693
+ lines.push('</tbody>');
694
+ lines.push('</table>');
695
+ lines.push('</section>');
696
+ }
662
697
  generateMermaidERD(schema) {
663
698
  const lines = [];
664
699
  lines.push('erDiagram');