@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.
- package/README.md +95 -0
- package/dist/commands/analyze.d.ts +1 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +6 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/export.d.ts +28 -19
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +91 -12
- package/dist/commands/export.js.map +1 -1
- package/dist/core/AnalysisEngine.d.ts +15 -6
- package/dist/core/AnalysisEngine.d.ts.map +1 -1
- package/dist/core/AnalysisEngine.js +41 -40
- package/dist/core/AnalysisEngine.js.map +1 -1
- package/dist/core/AnalysisOrchestrator.d.ts +20 -1
- package/dist/core/AnalysisOrchestrator.d.ts.map +1 -1
- package/dist/core/AnalysisOrchestrator.js +131 -27
- package/dist/core/AnalysisOrchestrator.js.map +1 -1
- package/dist/core/PKProposalGate.d.ts +22 -0
- package/dist/core/PKProposalGate.d.ts.map +1 -0
- package/dist/core/PKProposalGate.js +52 -0
- package/dist/core/PKProposalGate.js.map +1 -0
- package/dist/database/Database.d.ts +8 -0
- package/dist/database/Database.d.ts.map +1 -1
- package/dist/database/Database.js +23 -1
- package/dist/database/Database.js.map +1 -1
- package/dist/discovery/EnumCandidateGate.d.ts +19 -0
- package/dist/discovery/EnumCandidateGate.d.ts.map +1 -1
- package/dist/discovery/EnumCandidateGate.js +47 -5
- package/dist/discovery/EnumCandidateGate.js.map +1 -1
- package/dist/discovery/FKGraphWalker.d.ts +35 -0
- package/dist/discovery/FKGraphWalker.d.ts.map +1 -1
- package/dist/discovery/FKGraphWalker.js +113 -12
- package/dist/discovery/FKGraphWalker.js.map +1 -1
- package/dist/discovery/LLMDiscoveryValidator.d.ts +2 -0
- package/dist/discovery/LLMDiscoveryValidator.d.ts.map +1 -1
- package/dist/discovery/LLMDiscoveryValidator.js +20 -9
- package/dist/discovery/LLMDiscoveryValidator.js.map +1 -1
- package/dist/discovery/OrganicKeyDetector.d.ts.map +1 -1
- package/dist/discovery/OrganicKeyDetector.js +8 -2
- package/dist/discovery/OrganicKeyDetector.js.map +1 -1
- package/dist/discovery/StructuralPhase.d.ts +27 -5
- package/dist/discovery/StructuralPhase.d.ts.map +1 -1
- package/dist/discovery/StructuralPhase.js +42 -9
- package/dist/discovery/StructuralPhase.js.map +1 -1
- package/dist/discovery/TransitiveBridgeDetector.d.ts +5 -1
- package/dist/discovery/TransitiveBridgeDetector.d.ts.map +1 -1
- package/dist/discovery/TransitiveBridgeDetector.js +25 -3
- package/dist/discovery/TransitiveBridgeDetector.js.map +1 -1
- package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -1
- package/dist/drivers/BaseAutoDocDriver.js +3 -5
- package/dist/drivers/BaseAutoDocDriver.js.map +1 -1
- package/dist/drivers/PostgreSQLDriver.d.ts +9 -1
- package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -1
- package/dist/drivers/PostgreSQLDriver.js +41 -19
- package/dist/drivers/PostgreSQLDriver.js.map +1 -1
- package/dist/drivers/SQLServerDriver.d.ts.map +1 -1
- package/dist/drivers/SQLServerDriver.js +2 -1
- package/dist/drivers/SQLServerDriver.js.map +1 -1
- package/dist/generators/AdditionalSchemaInfoGenerator.d.ts +34 -0
- package/dist/generators/AdditionalSchemaInfoGenerator.d.ts.map +1 -1
- package/dist/generators/AdditionalSchemaInfoGenerator.js +85 -4
- package/dist/generators/AdditionalSchemaInfoGenerator.js.map +1 -1
- package/dist/generators/ErdClusterGenerator.d.ts +44 -0
- package/dist/generators/ErdClusterGenerator.d.ts.map +1 -0
- package/dist/generators/ErdClusterGenerator.js +216 -0
- package/dist/generators/ErdClusterGenerator.js.map +1 -0
- package/dist/generators/ErdClusterer.d.ts +130 -0
- package/dist/generators/ErdClusterer.d.ts.map +1 -0
- package/dist/generators/ErdClusterer.js +308 -0
- package/dist/generators/ErdClusterer.js.map +1 -0
- package/dist/generators/HTMLGenerator.d.ts +6 -0
- package/dist/generators/HTMLGenerator.d.ts.map +1 -1
- package/dist/generators/HTMLGenerator.js +41 -6
- package/dist/generators/HTMLGenerator.js.map +1 -1
- package/dist/generators/MermaidGenerator.d.ts +3 -0
- package/dist/generators/MermaidGenerator.d.ts.map +1 -1
- package/dist/generators/MermaidGenerator.js +4 -0
- package/dist/generators/MermaidGenerator.js.map +1 -1
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/types/analysis.d.ts +2 -0
- package/dist/types/analysis.d.ts.map +1 -1
- package/dist/types/config.d.ts +46 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +7 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/discovery.d.ts +2 -0
- package/dist/types/discovery.d.ts.map +1 -1
- package/dist/types/state.d.ts +27 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/utils/config-loader.d.ts +5 -0
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +37 -0
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/index-hints.d.ts +36 -0
- package/dist/utils/index-hints.d.ts.map +1 -0
- package/dist/utils/index-hints.js +65 -0
- package/dist/utils/index-hints.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/llm-factory.d.ts +1 -0
- package/dist/utils/llm-factory.d.ts.map +1 -1
- package/dist/utils/llm-factory.js +6 -1
- package/dist/utils/llm-factory.js.map +1 -1
- package/package.json +5 -5
- package/prompts/fk-pruning-table.md +13 -1
- package/prompts/pk-pruning-table.md +10 -0
- 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;
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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');
|