sqlite-hub 0.6.0 → 0.8.7

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 (44) hide show
  1. package/README.md +107 -0
  2. package/bin/sqlite-hub.js +285 -4
  3. package/changelog.md +21 -0
  4. package/docs/DESIGN_GUIDELINES.md +36 -0
  5. package/frontend/assets/mockups/sql_editor_croped.png +0 -0
  6. package/frontend/index.html +80 -0
  7. package/frontend/js/api.js +34 -0
  8. package/frontend/js/app.js +188 -10
  9. package/frontend/js/components/connectionCard.js +3 -4
  10. package/frontend/js/components/emptyState.js +4 -4
  11. package/frontend/js/components/modal.js +341 -24
  12. package/frontend/js/components/queryChartRenderer.js +125 -0
  13. package/frontend/js/components/queryEditor.js +33 -6
  14. package/frontend/js/components/queryHistoryDetail.js +16 -7
  15. package/frontend/js/components/queryHistoryPanel.js +18 -7
  16. package/frontend/js/components/rowEditorPanel.js +59 -54
  17. package/frontend/js/components/sidebar.js +32 -32
  18. package/frontend/js/components/structureGraph.js +54 -122
  19. package/frontend/js/components/tableDesignerEditor.js +9 -8
  20. package/frontend/js/components/tableDesignerSidebar.js +52 -48
  21. package/frontend/js/components/tableDesignerSqlPreview.js +60 -28
  22. package/frontend/js/lib/queryChartOptions.js +283 -0
  23. package/frontend/js/lib/queryCharts.js +560 -0
  24. package/frontend/js/router.js +8 -0
  25. package/frontend/js/store.js +641 -0
  26. package/frontend/js/views/charts.js +499 -0
  27. package/frontend/js/views/connections.js +5 -17
  28. package/frontend/js/views/data.js +40 -27
  29. package/frontend/js/views/editor.js +19 -13
  30. package/frontend/js/views/overview.js +149 -10
  31. package/frontend/js/views/settings.js +2 -2
  32. package/frontend/js/views/structure.js +105 -59
  33. package/frontend/js/views/tableDesigner.js +7 -2
  34. package/frontend/styles/base.css +62 -0
  35. package/frontend/styles/components.css +68 -118
  36. package/frontend/styles/structure-graph.css +19 -82
  37. package/frontend/styles/tokens.css +2 -0
  38. package/frontend/styles/views.css +293 -66
  39. package/package.json +2 -1
  40. package/server/routes/charts.js +153 -0
  41. package/server/server.js +6 -0
  42. package/server/services/sqlite/overviewService.js +68 -0
  43. package/server/services/storage/appStateStore.js +400 -1
  44. package/server/services/storage/queryHistoryChartUtils.js +145 -0
@@ -1,124 +1,351 @@
1
1
  .machined-grid {
2
- background-image: radial-gradient(var(--color-grid-dot) 1px, transparent 1px);
3
- background-size: 32px 32px;
2
+ background-image: radial-gradient(var(--color-grid-dot) 1px, transparent 1px);
3
+ background-size: 32px 32px;
4
4
  }
5
5
 
6
6
  .data-grid-texture {
7
- background-image: radial-gradient(var(--color-primary-container) 1px, transparent 1px);
8
- background-size: 24px 24px;
9
- opacity: 0.03;
7
+ background-image: radial-gradient(var(--color-primary-container) 1px, transparent 1px);
8
+ background-size: 24px 24px;
9
+ opacity: 0.03;
10
10
  }
11
11
 
12
12
  .view-surface {
13
- background: var(--color-surface);
14
- min-height: 100%;
13
+ background: var(--color-surface);
14
+ min-height: 100%;
15
15
  }
16
16
 
17
17
  .view-frame {
18
- min-height: 100%;
19
- padding: var(--spacing-8);
18
+ min-height: 100%;
19
+ padding: var(--spacing-8);
20
20
  }
21
21
 
22
22
  .view-stack {
23
- display: flex;
24
- flex-direction: column;
25
- gap: var(--spacing-8);
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--spacing-8);
26
26
  }
27
27
 
28
28
  .table-designer-view {
29
- display: flex;
30
- height: 100%;
31
- min-height: 0;
32
- overflow: hidden;
29
+ display: flex;
30
+ height: 100%;
31
+ min-height: 0;
32
+ overflow: hidden;
33
33
  }
34
34
 
35
35
  .table-designer-workspace {
36
- display: flex;
37
- flex: 1;
38
- flex-direction: column;
39
- gap: var(--spacing-4);
40
- min-height: 0;
41
- min-width: 0;
36
+ display: flex;
37
+ flex: 1;
38
+ flex-direction: column;
39
+ gap: var(--spacing-4);
40
+ min-height: 0;
41
+ min-width: 0;
42
42
  }
43
43
 
44
44
  .table-designer-workspace__top,
45
45
  .table-designer-workspace__bottom {
46
- min-height: 0;
47
- min-width: 0;
46
+ min-height: 0;
47
+ min-width: 0;
48
48
  }
49
49
 
50
50
  .table-designer-workspace__top {
51
- flex: 1;
51
+ flex: 1;
52
52
  }
53
53
 
54
54
  .table-designer-workspace__bottom {
55
- flex: 0 0 34%;
55
+ flex: 0 0 34%;
56
+ }
57
+
58
+ .table-designer-workspace__bottom.is-collapsed {
59
+ flex: 0 0 auto;
56
60
  }
57
61
 
58
62
  .landing-view {
59
- align-items: center;
60
- display: flex;
61
- justify-content: center;
62
- min-height: 100%;
63
- overflow: hidden;
64
- position: relative;
63
+ align-items: center;
64
+ display: flex;
65
+ justify-content: center;
66
+ min-height: 100%;
67
+ overflow: hidden;
68
+ position: relative;
65
69
  }
66
70
 
67
71
  .landing-accent {
68
- background: rgba(75, 71, 50, 0.2);
69
- height: 1px;
70
- position: absolute;
72
+ background: rgba(75, 71, 50, 0.2);
73
+ height: 1px;
74
+ position: absolute;
71
75
  }
72
76
 
73
77
  .landing-accent--a {
74
- left: 3rem;
75
- top: 2.5rem;
76
- transform: rotate(45deg);
77
- width: 8rem;
78
+ left: 3rem;
79
+ top: 2.5rem;
80
+ transform: rotate(45deg);
81
+ width: 8rem;
78
82
  }
79
83
 
80
84
  .landing-accent--b {
81
- bottom: 2.5rem;
82
- right: 3rem;
83
- transform: rotate(-12deg);
84
- width: 16rem;
85
+ bottom: 2.5rem;
86
+ right: 3rem;
87
+ transform: rotate(-12deg);
88
+ width: 16rem;
85
89
  }
86
90
 
87
91
  .landing-accent--c {
88
- border-bottom: 1px solid rgba(75, 71, 50, 0.12);
89
- border-right: 1px solid rgba(75, 71, 50, 0.12);
90
- height: 4rem;
91
- left: 0;
92
- top: 50%;
93
- width: 4rem;
92
+ border-bottom: 1px solid rgba(75, 71, 50, 0.12);
93
+ border-right: 1px solid rgba(75, 71, 50, 0.12);
94
+ height: 4rem;
95
+ left: 0;
96
+ top: 50%;
97
+ width: 4rem;
94
98
  }
95
99
 
96
100
  .system-placeholder-grid {
97
- display: grid;
98
- gap: var(--spacing-4);
99
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
101
+ display: grid;
102
+ gap: var(--spacing-4);
103
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
100
104
  }
101
105
 
102
106
  .system-placeholder-card {
103
- background: var(--color-surface-low);
104
- border-bottom: 2px solid rgba(49, 48, 48, 1);
105
- display: flex;
106
- flex-direction: column;
107
- gap: var(--spacing-2);
108
- min-height: 160px;
109
- padding: var(--spacing-6);
107
+ background: var(--color-surface-low);
108
+ border-bottom: 2px solid rgba(49, 48, 48, 1);
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: var(--spacing-2);
112
+ min-height: 160px;
113
+ padding: var(--spacing-6);
110
114
  }
111
115
 
112
116
  .system-placeholder-card.is-primary {
113
- border-bottom-color: var(--color-primary-container);
117
+ border-bottom-color: var(--color-primary-container);
114
118
  }
115
119
 
116
- @media (max-width: 1023px) {
117
- .table-designer-view {
120
+ .charts-view {
121
+ background: var(--color-surface);
122
+ display: flex;
123
+ height: 100%;
124
+ min-height: 0;
125
+ overflow: hidden;
126
+ }
127
+
128
+ .charts-view__sidebar {
129
+ background: var(--color-surface-low);
130
+ border-right: 1px solid rgba(75, 71, 50, 0.18);
131
+ display: flex;
132
+ flex: 0 0 23rem;
133
+ flex-direction: column;
134
+ min-height: 0;
135
+ width: 23rem;
136
+ }
137
+
138
+ .charts-view__sidebar-header {
139
+ border-bottom: 1px solid rgba(75, 71, 50, 0.18);
140
+ padding: var(--spacing-5);
141
+ }
142
+
143
+ .structure-view {
144
+ background: var(--color-surface);
145
+ display: flex;
146
+ height: 100%;
147
+ min-height: 0;
148
+ overflow: hidden;
149
+ }
150
+
151
+ .structure-view__sidebar {
152
+ background: var(--color-surface-low);
153
+ border-right: 1px solid rgba(75, 71, 50, 0.18);
154
+ display: flex;
155
+ flex: 0 0 18.5rem;
118
156
  flex-direction: column;
119
- }
157
+ min-height: 0;
158
+ width: 18.5rem;
159
+ }
160
+
161
+ .structure-view__sidebar-header {
162
+ border-bottom: 1px solid rgba(75, 71, 50, 0.18);
163
+ padding: var(--spacing-5);
164
+ }
165
+
166
+ .structure-view__sidebar-body {
167
+ flex: 1;
168
+ min-height: 0;
169
+ overflow-y: auto;
170
+ padding: var(--spacing-4);
171
+ }
172
+
173
+ .structure-view__detail {
174
+ display: flex;
175
+ flex: 1;
176
+ flex-direction: column;
177
+ min-height: 0;
178
+ min-width: 0;
179
+ }
180
+
181
+ .structure-view__header {
182
+ border-bottom: 1px solid rgba(75, 71, 50, 0.18);
183
+ padding: 1.25rem 1.5rem;
184
+ }
185
+
186
+ .structure-headline-container {
187
+ max-width: min(64rem, 100%);
188
+ }
189
+
190
+ .structure-view__graph-shell {
191
+ flex: 1;
192
+ min-height: 0;
193
+ }
194
+
195
+ .structure-sidebar__section + .structure-sidebar__section {
196
+ margin-top: 1.5rem;
197
+ }
198
+
199
+ .charts-view__detail {
200
+ display: flex;
201
+ flex: 1;
202
+ flex-direction: column;
203
+ min-height: 0;
204
+ min-width: 0;
205
+ }
206
+
207
+ .charts-detail-shell {
208
+ min-height: 100%;
209
+ padding: var(--spacing-6);
210
+ }
211
+
212
+ .charts-detail-shell__header {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: var(--spacing-4);
216
+ }
217
+
218
+ .charts-detail-shell__title {
219
+ min-width: 0;
220
+ }
221
+
222
+ .charts-detail-shell__controls {
223
+ align-items: end;
224
+ display: flex;
225
+ flex-wrap: wrap;
226
+ gap: var(--spacing-3);
227
+ justify-content: space-between;
228
+ }
229
+
230
+ .charts-detail-shell__controls-group {
231
+ align-items: end;
232
+ display: flex;
233
+ flex-wrap: wrap;
234
+ gap: var(--spacing-3);
235
+ }
236
+
237
+ .charts-detail-shell__controls-group--end {
238
+ justify-content: flex-end;
239
+ margin-left: auto;
240
+ }
241
+
242
+ .charts-height-toggle {
243
+ background: var(--color-surface-container-low);
244
+ border: 1px solid rgba(75, 71, 50, 0.18);
245
+ display: inline-flex;
246
+ gap: 0;
247
+ height: var(--control-height);
248
+ overflow: hidden;
249
+ }
250
+
251
+ .charts-height-toggle__button {
252
+ align-items: center;
253
+ background: transparent;
254
+ border: none;
255
+ box-sizing: border-box;
256
+ color: var(--color-on-surface-variant);
257
+ display: inline-flex;
258
+ font-family: var(--font-family-mono);
259
+ font-size: 0.625rem;
260
+ font-weight: 700;
261
+ height: var(--control-height);
262
+ justify-content: center;
263
+ line-height: 1;
264
+ letter-spacing: 0.16em;
265
+ padding: 0 1rem;
266
+ }
267
+
268
+ .charts-height-toggle__button + .charts-height-toggle__button {
269
+ border-left: 1px solid rgba(75, 71, 50, 0.18);
270
+ }
271
+
272
+ .charts-height-toggle__button.is-active {
273
+ background: var(--color-primary-container);
274
+ border-color: transparent;
275
+ color: var(--color-on-primary);
276
+ }
277
+
278
+ .query-chart-card {
279
+ --query-chart-height: 450px;
280
+ background: var(--color-surface-low);
281
+ border: 1px solid rgba(75, 71, 50, 0.12);
282
+ overflow: hidden;
283
+ }
284
+
285
+ .query-chart-card--small {
286
+ --query-chart-height: 300px;
287
+ }
288
+
289
+ .query-chart-card--medium {
290
+ --query-chart-height: 450px;
291
+ }
292
+
293
+ .query-chart-card--large {
294
+ --query-chart-height: 600px;
295
+ }
296
+
297
+ .query-chart-card__header {
298
+ align-items: center;
299
+ background: var(--color-surface-container-highest);
300
+ display: flex;
301
+ flex-wrap: wrap;
302
+ gap: var(--spacing-4);
303
+ justify-content: space-between;
304
+ padding: 1rem 1.25rem;
305
+ }
306
+
307
+ .query-chart-card__body {
308
+ background:
309
+ radial-gradient(circle at top right, rgba(252, 227, 0, 0.08), transparent 28%),
310
+ linear-gradient(180deg, rgba(32, 31, 31, 0.92), rgba(14, 14, 14, 1));
311
+ min-height: var(--query-chart-height);
312
+ padding: 1rem;
313
+ }
314
+
315
+ .query-chart-canvas,
316
+ .query-chart-surface-state {
317
+ height: var(--query-chart-height);
318
+ min-height: var(--query-chart-height);
319
+ }
320
+
321
+ .query-chart-card__results {
322
+ background: var(--color-surface-container-lowest);
323
+ }
324
+
325
+ @media (max-width: 1023px) {
326
+ .charts-view {
327
+ flex-direction: column;
328
+ }
329
+
330
+ .charts-view__sidebar {
331
+ flex-basis: 18rem;
332
+ width: 100%;
333
+ }
334
+
335
+ .structure-view {
336
+ flex-direction: column;
337
+ }
338
+
339
+ .structure-view__sidebar {
340
+ flex-basis: 18rem;
341
+ width: 100%;
342
+ }
343
+
344
+ .table-designer-view {
345
+ flex-direction: column;
346
+ }
120
347
 
121
- .table-designer-workspace__bottom {
122
- flex-basis: 280px;
123
- }
348
+ .table-designer-workspace__bottom {
349
+ flex-basis: 280px;
350
+ }
124
351
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlite-hub",
3
- "version": "0.6.0",
3
+ "version": "0.8.7",
4
4
  "description": "SQLite-only local management app backend and SPA shell",
5
5
  "main": "server/server.js",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "better-sqlite3": "^11.8.1",
16
16
  "cytoscape": "^3.33.2",
17
17
  "cytoscape-elk": "^2.3.0",
18
+ "echarts": "^5.6.0",
18
19
  "elkjs": "^0.11.1",
19
20
  "express": "^4.21.2"
20
21
  }
@@ -0,0 +1,153 @@
1
+ const express = require("express");
2
+ const {
3
+ DatabaseRequiredError,
4
+ route,
5
+ successResponse,
6
+ } = require("../utils/errors");
7
+
8
+ function getActiveDatabaseKey(connectionManager) {
9
+ return connectionManager.getActiveConnection()?.id ?? null;
10
+ }
11
+
12
+ function requireActiveDatabaseKey(connectionManager) {
13
+ const databaseKey = getActiveDatabaseKey(connectionManager);
14
+
15
+ if (!databaseKey) {
16
+ throw new DatabaseRequiredError();
17
+ }
18
+
19
+ return databaseKey;
20
+ }
21
+
22
+ function createChartsRouter({ appStateStore, connectionManager, sqlExecutor }) {
23
+ const router = express.Router();
24
+
25
+ router.get(
26
+ "/query-history",
27
+ route((req, res) => {
28
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
29
+
30
+ res.json(
31
+ successResponse({
32
+ data: appStateStore.getChartQueryHistoryList(databaseKey),
33
+ metadata: { databaseKey },
34
+ })
35
+ );
36
+ })
37
+ );
38
+
39
+ router.get(
40
+ "/query-history/:historyId",
41
+ route((req, res) => {
42
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
43
+
44
+ res.json(
45
+ successResponse({
46
+ data: appStateStore.getQueryHistoryChartsDetail(req.params.historyId, databaseKey),
47
+ metadata: {
48
+ databaseKey,
49
+ historyId: Number(req.params.historyId),
50
+ },
51
+ })
52
+ );
53
+ })
54
+ );
55
+
56
+ router.post(
57
+ "/query-history/:historyId/execute",
58
+ route((req, res) => {
59
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
60
+ const item = appStateStore.getQueryHistoryItemForDatabase(req.params.historyId, databaseKey);
61
+ const result = sqlExecutor.execute(item.rawSql, {
62
+ persistHistory: false,
63
+ requireReader: true,
64
+ });
65
+
66
+ res.json(
67
+ successResponse({
68
+ message: "Query results loaded for charts.",
69
+ data: {
70
+ ...result,
71
+ queryHistoryId: item.id,
72
+ },
73
+ metadata: {
74
+ databaseKey,
75
+ historyId: item.id,
76
+ },
77
+ timingMs: result.timingMs,
78
+ })
79
+ );
80
+ })
81
+ );
82
+
83
+ router.post(
84
+ "/",
85
+ route((req, res) => {
86
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
87
+ const chart = appStateStore.createQueryHistoryChart({
88
+ databaseKey,
89
+ queryHistoryId: req.body?.queryHistoryId,
90
+ name: req.body?.name,
91
+ chartType: req.body?.chartType,
92
+ config: req.body?.config,
93
+ resultColumns: req.body?.resultColumns,
94
+ tableVisible: req.body?.tableVisible,
95
+ });
96
+
97
+ res.json(
98
+ successResponse({
99
+ message: "Chart created.",
100
+ data: chart,
101
+ metadata: { databaseKey },
102
+ })
103
+ );
104
+ })
105
+ );
106
+
107
+ router.patch(
108
+ "/:chartId",
109
+ route((req, res) => {
110
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
111
+ const chart = appStateStore.updateQueryHistoryChart(req.params.chartId, {
112
+ databaseKey,
113
+ name: req.body?.name,
114
+ chartType: req.body?.chartType,
115
+ config: req.body?.config,
116
+ resultColumns: req.body?.resultColumns,
117
+ tableVisible: req.body?.tableVisible,
118
+ });
119
+
120
+ res.json(
121
+ successResponse({
122
+ message: "Chart updated.",
123
+ data: chart,
124
+ metadata: { databaseKey },
125
+ })
126
+ );
127
+ })
128
+ );
129
+
130
+ router.delete(
131
+ "/:chartId",
132
+ route((req, res) => {
133
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
134
+ appStateStore.deleteQueryHistoryChart(req.params.chartId, databaseKey);
135
+
136
+ res.json(
137
+ successResponse({
138
+ message: "Chart deleted.",
139
+ data: {
140
+ id: Number(req.params.chartId),
141
+ },
142
+ metadata: { databaseKey },
143
+ })
144
+ );
145
+ })
146
+ );
147
+
148
+ return router;
149
+ }
150
+
151
+ module.exports = {
152
+ createChartsRouter,
153
+ };
package/server/server.js CHANGED
@@ -15,6 +15,7 @@ const { TableDesignerService } = require("./services/sqlite/tableDesignerService
15
15
  const { createConnectionsRouter } = require("./routes/connections");
16
16
  const { createOverviewRouter } = require("./routes/overview");
17
17
  const { createSqlRouter } = require("./routes/sql");
18
+ const { createChartsRouter } = require("./routes/charts");
18
19
  const { createStructureRouter } = require("./routes/structure");
19
20
  const { createDataRouter } = require("./routes/data");
20
21
  const { createTableDesignerRouter } = require("./routes/tableDesigner");
@@ -79,6 +80,7 @@ app.use(
79
80
  );
80
81
  app.use("/api/db", createOverviewRouter({ overviewService }));
81
82
  app.use("/api/sql", createSqlRouter({ appStateStore, connectionManager, sqlExecutor }));
83
+ app.use("/api/charts", createChartsRouter({ appStateStore, connectionManager, sqlExecutor }));
82
84
  app.use("/api/structure", createStructureRouter({ structureService }));
83
85
  app.use("/api/data", createDataRouter({ dataBrowserService }));
84
86
  app.use("/api/table-designer", createTableDesignerRouter({ tableDesignerService }));
@@ -109,6 +111,10 @@ app.use(
109
111
  "/vendor/elkjs",
110
112
  express.static(path.resolve(__dirname, "..", "node_modules", "elkjs"))
111
113
  );
114
+ app.use(
115
+ "/vendor/echarts",
116
+ express.static(path.resolve(__dirname, "..", "node_modules", "echarts"))
117
+ );
112
118
  app.use(express.static(FRONTEND_ROOT));
113
119
  app.use("/db_logos", express.static(path.join(APP_STATE_DIRECTORY, "db_logos")));
114
120
  app.use(errorMiddleware);
@@ -12,6 +12,73 @@ class OverviewService {
12
12
  this.connectionManager = connectionManager;
13
13
  }
14
14
 
15
+ getSchemaMapPreview(schema) {
16
+ const tables = schema?.tables ?? [];
17
+ const indexes = schema?.indexes ?? [];
18
+ const tableNames = new Set(tables.map((table) => table.name));
19
+ const adjacency = new Map(tables.map((table) => [table.name, new Set()]));
20
+ let relationshipCount = 0;
21
+
22
+ tables.forEach((table) => {
23
+ (table.foreignKeys ?? []).forEach((foreignKey) => {
24
+ if (!tableNames.has(foreignKey.referencedTable)) {
25
+ return;
26
+ }
27
+
28
+ relationshipCount += foreignKey.mappings?.length ?? 0;
29
+ adjacency.get(table.name)?.add(foreignKey.referencedTable);
30
+ adjacency.get(foreignKey.referencedTable)?.add(table.name);
31
+ });
32
+ });
33
+
34
+ let fkClusters = 0;
35
+ let isolatedTables = 0;
36
+ const visited = new Set();
37
+
38
+ tables.forEach((table) => {
39
+ if (visited.has(table.name)) {
40
+ return;
41
+ }
42
+
43
+ const stack = [table.name];
44
+ let componentHasRelationships = false;
45
+
46
+ visited.add(table.name);
47
+
48
+ while (stack.length) {
49
+ const current = stack.pop();
50
+ const neighbors = adjacency.get(current) ?? new Set();
51
+
52
+ if (neighbors.size > 0) {
53
+ componentHasRelationships = true;
54
+ }
55
+
56
+ neighbors.forEach((neighbor) => {
57
+ if (visited.has(neighbor)) {
58
+ return;
59
+ }
60
+
61
+ visited.add(neighbor);
62
+ stack.push(neighbor);
63
+ });
64
+ }
65
+
66
+ if (componentHasRelationships) {
67
+ fkClusters += 1;
68
+ } else {
69
+ isolatedTables += 1;
70
+ }
71
+ });
72
+
73
+ return {
74
+ tableCount: tables.length,
75
+ indexCount: indexes.length,
76
+ relationshipCount,
77
+ fkClusters,
78
+ isolatedTables,
79
+ };
80
+ }
81
+
15
82
  safePragmaValue(db, pragmaName) {
16
83
  const row = db.prepare(`PRAGMA ${pragmaName}`).get();
17
84
  return row ? Object.values(row)[0] : null;
@@ -78,6 +145,7 @@ class OverviewService {
78
145
  indexes: schema.indexes.length,
79
146
  triggers: schema.triggers.length,
80
147
  },
148
+ schemaMap: this.getSchemaMapPreview(schema),
81
149
  topTablesByRowCount: [...schema.tables]
82
150
  .sort((left, right) => (right.rowCount ?? 0) - (left.rowCount ?? 0))
83
151
  .slice(0, 10)