sqlite-hub 0.5.0 → 0.7.0

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 (50) hide show
  1. package/README.md +109 -2
  2. package/bin/sqlite-hub.js +285 -4
  3. package/changelog.md +15 -0
  4. package/frontend/assets/mockups/connections.png +0 -0
  5. package/frontend/assets/mockups/data.png +0 -0
  6. package/frontend/assets/mockups/data_row_editor.png +0 -0
  7. package/frontend/assets/mockups/home.png +0 -0
  8. package/frontend/assets/mockups/sql_editor.png +0 -0
  9. package/frontend/assets/mockups/sql_editor_croped.png +0 -0
  10. package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
  11. package/frontend/assets/mockups/structure.png +0 -0
  12. package/frontend/assets/mockups/structure_inspector.png +0 -0
  13. package/frontend/index.html +1 -0
  14. package/frontend/js/api.js +49 -0
  15. package/frontend/js/app.js +377 -9
  16. package/frontend/js/components/modal.js +314 -1
  17. package/frontend/js/components/queryChartRenderer.js +125 -0
  18. package/frontend/js/components/queryEditor.js +37 -8
  19. package/frontend/js/components/queryHistoryPanel.js +16 -5
  20. package/frontend/js/components/sidebar.js +2 -0
  21. package/frontend/js/components/tableDesignerEditor.js +356 -0
  22. package/frontend/js/components/tableDesignerSidebar.js +129 -0
  23. package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
  24. package/frontend/js/lib/queryChartOptions.js +283 -0
  25. package/frontend/js/lib/queryCharts.js +560 -0
  26. package/frontend/js/router.js +18 -0
  27. package/frontend/js/store.js +914 -1
  28. package/frontend/js/utils/tableDesigner.js +1192 -0
  29. package/frontend/js/views/charts.js +471 -0
  30. package/frontend/js/views/data.js +281 -277
  31. package/frontend/js/views/editor.js +19 -13
  32. package/frontend/js/views/structure.js +81 -39
  33. package/frontend/js/views/tableDesigner.js +37 -0
  34. package/frontend/styles/base.css +87 -73
  35. package/frontend/styles/components.css +790 -251
  36. package/frontend/styles/views.css +316 -46
  37. package/package.json +2 -1
  38. package/server/routes/charts.js +153 -0
  39. package/server/routes/tableDesigner.js +60 -0
  40. package/server/server.js +10 -0
  41. package/server/services/sqlite/tableDesigner/changeAnalysis.js +295 -0
  42. package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
  43. package/server/services/sqlite/tableDesigner/sql.js +63 -0
  44. package/server/services/sqlite/tableDesigner/validation.js +245 -0
  45. package/server/services/sqlite/tableDesignerService.js +181 -0
  46. package/server/services/storage/appStateStore.js +400 -1
  47. package/server/services/storage/queryHistoryChartUtils.js +145 -0
  48. package/frontend/assets/mockups/data_edit.png +0 -0
  49. package/frontend/assets/mockups/graph_visualize.png +0 -0
  50. package/frontend/assets/mockups/overview.png +0 -0
@@ -236,7 +236,7 @@ function renderResultsSurface(state, isResultsRoute) {
236
236
 
237
237
  export function renderEditorView(state, { isResultsRoute = false } = {}) {
238
238
  const connection = getCurrentConnection(state);
239
- const editorSectionClass = "min-h-[27.5%]";
239
+ const editorSectionClass = state.editor.editorPanelVisible ? "min-h-[27.5%]" : "flex-none";
240
240
  const resultsSectionClass = "flex-1";
241
241
 
242
242
  return {
@@ -252,6 +252,8 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
252
252
  exporting: state.editor.exportLoading,
253
253
  historyLoading: state.editor.historyLoading,
254
254
  historyTotal: state.editor.historyTotal,
255
+ editorVisible: state.editor.editorPanelVisible,
256
+ historyVisible: state.editor.historyPanelVisible,
255
257
  title: connection?.label ?? "SQLite Query Workspace",
256
258
  })}
257
259
  </section>
@@ -259,18 +261,22 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
259
261
  ${renderResultsSurface(state, isResultsRoute)}
260
262
  </section>
261
263
  </div>
262
- ${renderQueryHistoryPanel({
263
- items: state.editor.history,
264
- loading: state.editor.historyLoading,
265
- loadingMore: state.editor.historyLoadingMore,
266
- error: state.editor.historyError,
267
- activeTab: state.editor.historyTab,
268
- search: state.editor.historySearchInput,
269
- total: state.editor.historyTotal,
270
- hasMore: state.editor.historyHasMore,
271
- activeHistoryId: state.editor.historyActiveId,
272
- selectedHistoryId: state.editor.historySelectedId,
273
- })}
264
+ ${
265
+ state.editor.historyPanelVisible
266
+ ? renderQueryHistoryPanel({
267
+ items: state.editor.history,
268
+ loading: state.editor.historyLoading,
269
+ loadingMore: state.editor.historyLoadingMore,
270
+ error: state.editor.historyError,
271
+ activeTab: state.editor.historyTab,
272
+ search: state.editor.historySearchInput,
273
+ total: state.editor.historyTotal,
274
+ hasMore: state.editor.historyHasMore,
275
+ activeHistoryId: state.editor.historyActiveId,
276
+ selectedHistoryId: state.editor.historySelectedId,
277
+ })
278
+ : ""
279
+ }
274
280
  </section>
275
281
  `,
276
282
  panel:
@@ -1,4 +1,3 @@
1
- import { renderPageHeader } from "../components/pageHeader.js";
2
1
  import { clearInspector, renderInspector } from "../components/structureGraph.js";
3
2
  import { escapeHtml, formatNumber } from "../utils/format.js";
4
3
 
@@ -6,7 +5,7 @@ function renderEntryGroup(title, entries, activeName, options = {}) {
6
5
  const { compact = false, showMeta = true } = options;
7
6
 
8
7
  return `
9
- <section class="shell-section flex flex-col p-5">
8
+ <section class="structure-sidebar__section">
10
9
  <div class="mb-4 shrink-0 text-[10px] font-bold uppercase tracking-[0.25em] text-primary-container">
11
10
  ${escapeHtml(title)}
12
11
  </div>
@@ -282,6 +281,31 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading) {
282
281
  `;
283
282
  }
284
283
 
284
+ function renderStructureWorkspaceHeader(structure, selectedName) {
285
+ const tableCount = structure?.grouped?.tables?.length ?? 0;
286
+ const viewCount = structure?.grouped?.views?.length ?? 0;
287
+
288
+ return `
289
+ <header class="structure-view__header">
290
+ <div class="structure-headline-container">
291
+ <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
292
+ Schema Graph
293
+ </div>
294
+ <h1 class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
295
+ Structure
296
+ </h1>
297
+ <div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
298
+ ${
299
+ selectedName
300
+ ? `selected ${escapeHtml(selectedName)} // tables ${escapeHtml(formatNumber(tableCount))} // views ${escapeHtml(formatNumber(viewCount))}`
301
+ : `tables ${escapeHtml(formatNumber(tableCount))} // views ${escapeHtml(formatNumber(viewCount))}`
302
+ }
303
+ </div>
304
+ </div>
305
+ </header>
306
+ `;
307
+ }
308
+
285
309
  export function renderStructureView(state) {
286
310
  const structure = state.structure.data;
287
311
  const detail =
@@ -289,17 +313,61 @@ export function renderStructureView(state) {
289
313
 
290
314
  return {
291
315
  main: `
292
- <section class="view-surface flex h-full min-h-0 flex-col bg-surface-container">
293
- <div class="view-frame flex h-full min-h-0 flex-col">
294
- ${renderPageHeader({
295
- title: "Structure",
296
- subtitle: "Schema graph, foreign-key paths, raw DDL, and object metadata",
297
- })}
316
+ <section class="view-surface structure-view">
317
+ <aside class="structure-view__sidebar">
318
+ <div class="structure-view__sidebar-header">
319
+ <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
320
+ Objects
321
+ </div>
322
+ <div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
323
+ total ${escapeHtml(
324
+ formatNumber(
325
+ (structure?.grouped?.tables?.length ?? 0) +
326
+ (structure?.grouped?.views?.length ?? 0) +
327
+ (structure?.grouped?.indexes?.length ?? 0) +
328
+ (structure?.grouped?.triggers?.length ?? 0)
329
+ )
330
+ )}
331
+ </div>
332
+ </div>
333
+ <div class="structure-view__sidebar-body custom-scrollbar">
334
+ ${
335
+ structure
336
+ ? `
337
+ ${renderEntryGroup(
338
+ "Tables",
339
+ structure.grouped.tables,
340
+ state.structure.selectedName,
341
+ { compact: true, showMeta: false }
342
+ )}
343
+ ${renderEntryGroup(
344
+ "Views",
345
+ structure.grouped.views,
346
+ state.structure.selectedName
347
+ )}
348
+ ${renderEntryGroup(
349
+ "Indexes",
350
+ structure.grouped.indexes,
351
+ state.structure.selectedName,
352
+ { compact: true, showMeta: false }
353
+ )}
354
+ ${renderEntryGroup(
355
+ "Triggers",
356
+ structure.grouped.triggers,
357
+ state.structure.selectedName
358
+ )}
359
+ `
360
+ : ""
361
+ }
362
+ </div>
363
+ </aside>
298
364
 
365
+ <section class="structure-view__detail">
366
+ ${renderStructureWorkspaceHeader(structure, state.structure.selectedName)}
299
367
  ${
300
368
  state.structure.loading && !structure
301
369
  ? `
302
- <div class="flex min-h-0 flex-1 items-center justify-center border border-outline-variant/10 bg-surface-container-low">
370
+ <div class="flex min-h-0 flex-1 items-center justify-center border-t border-outline-variant/10 bg-surface-container-low">
303
371
  <div class="text-center text-on-surface-variant/40">
304
372
  <span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
305
373
  <p class="font-mono text-[10px] uppercase tracking-[0.22em]">LOADING_STRUCTURE</p>
@@ -308,7 +376,7 @@ export function renderStructureView(state) {
308
376
  `
309
377
  : state.structure.error
310
378
  ? `
311
- <div class="min-h-0 flex-1 border border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
379
+ <div class="min-h-0 flex-1 border-t border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
312
380
  <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
313
381
  ${escapeHtml(state.structure.error.code)}
314
382
  </div>
@@ -317,44 +385,18 @@ export function renderStructureView(state) {
317
385
  `
318
386
  : structure
319
387
  ? `
320
- <section class="grid min-h-0 flex-1 grid-cols-1 gap-6 xl:grid-cols-[18.5rem_minmax(0,1fr)] 2xl:grid-cols-[19.5rem_minmax(0,1fr)]">
321
- <div class="custom-scrollbar min-h-0 overflow-y-auto pr-1">
322
- <div class="space-y-6">
323
- ${renderEntryGroup(
324
- "Tables",
325
- structure.grouped.tables,
326
- state.structure.selectedName,
327
- { compact: true, showMeta: false }
328
- )}
329
- ${renderEntryGroup(
330
- "Views",
331
- structure.grouped.views,
332
- state.structure.selectedName
333
- )}
334
- ${renderEntryGroup(
335
- "Indexes",
336
- structure.grouped.indexes,
337
- state.structure.selectedName,
338
- { compact: true, showMeta: false }
339
- )}
340
- ${renderEntryGroup(
341
- "Triggers",
342
- structure.grouped.triggers,
343
- state.structure.selectedName
344
- )}
345
- </div>
346
- </div>
388
+ <div class="structure-view__graph-shell">
347
389
  ${renderGraphSurface(
348
390
  structure,
349
391
  state.structure.selectedName,
350
392
  detail,
351
393
  state.structure.detailLoading
352
394
  )}
353
- </section>
395
+ </div>
354
396
  `
355
397
  : ""
356
398
  }
357
- </div>
399
+ </section>
358
400
  </section>
359
401
  `,
360
402
  panel: "",
@@ -0,0 +1,37 @@
1
+ import { renderTableDesignerEditor } from "../components/tableDesignerEditor.js";
2
+ import { renderTableDesignerSidebar } from "../components/tableDesignerSidebar.js";
3
+ import { renderTableDesignerSqlPreview } from "../components/tableDesignerSqlPreview.js";
4
+ import { escapeHtml } from "../utils/format.js";
5
+
6
+ function renderRouteError(error) {
7
+ if (!error) {
8
+ return "";
9
+ }
10
+
11
+ return `
12
+ <div class="table-designer-route-error">
13
+ <div class="table-designer-route-error__code">${escapeHtml(error.code)}</div>
14
+ <div class="table-designer-route-error__text">${escapeHtml(error.message)}</div>
15
+ </div>
16
+ `;
17
+ }
18
+
19
+ export function renderTableDesignerView(state) {
20
+ return {
21
+ main: `
22
+ <section class="view-surface table-designer-view">
23
+ ${renderTableDesignerSidebar(state)}
24
+ <div class="table-designer-workspace">
25
+ ${renderRouteError(state.tableDesigner.error)}
26
+ <div class="table-designer-workspace__top">
27
+ ${renderTableDesignerEditor(state)}
28
+ </div>
29
+ <div class="table-designer-workspace__bottom">
30
+ ${renderTableDesignerSqlPreview(state.tableDesigner.draft)}
31
+ </div>
32
+ </div>
33
+ </section>
34
+ `,
35
+ panel: "",
36
+ };
37
+ }
@@ -1,103 +1,110 @@
1
1
  *,
2
2
  *::before,
3
3
  *::after {
4
- box-sizing: border-box;
4
+ box-sizing: border-box;
5
5
  }
6
6
 
7
7
  html,
8
8
  body {
9
- height: 100%;
10
- margin: 0;
9
+ height: 100%;
10
+ margin: 0;
11
11
  }
12
12
 
13
13
  body {
14
- background: var(--color-background);
15
- color: var(--color-on-surface);
16
- font-family: var(--font-family-body);
14
+ background: var(--color-background);
15
+ color: var(--color-on-surface);
16
+ font-family: var(--font-family-body);
17
17
  }
18
18
 
19
19
  button,
20
20
  input,
21
21
  select,
22
22
  textarea {
23
- color: inherit;
24
- font: inherit;
23
+ color: inherit;
24
+ font: inherit;
25
25
  }
26
26
 
27
27
  button {
28
- cursor: pointer;
28
+ cursor: pointer;
29
29
  }
30
30
 
31
31
  a {
32
- color: inherit;
33
- text-decoration: none;
32
+ color: inherit;
33
+ text-decoration: none;
34
34
  }
35
35
 
36
36
  img {
37
- display: block;
38
- max-width: 100%;
37
+ display: block;
38
+ max-width: 100%;
39
39
  }
40
40
 
41
41
  [hidden] {
42
- display: none !important;
42
+ display: none !important;
43
43
  }
44
44
 
45
45
  .font-headline {
46
- font-family: var(--font-family-headline);
46
+ font-family: var(--font-family-headline);
47
47
  }
48
48
 
49
49
  .font-body {
50
- font-family: var(--font-family-body);
50
+ font-family: var(--font-family-body);
51
51
  }
52
52
 
53
53
  .font-mono {
54
- font-family: var(--font-family-mono);
54
+ font-family: var(--font-family-mono);
55
+ overflow: hidden;
56
+ white-space: nowrap;
57
+ text-overflow: ellipsis;
55
58
  }
56
59
 
57
60
  .material-symbols-outlined {
58
- display: inline-block;
59
- font-size: var(--font-size-icon);
60
- font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24;
61
- letter-spacing: normal;
62
- line-height: 1;
63
- text-transform: none;
64
- white-space: nowrap;
65
- word-wrap: normal;
66
- direction: ltr;
67
- vertical-align: middle;
61
+ display: inline-block;
62
+ font-size: var(--font-size-icon);
63
+ font-variation-settings:
64
+ 'FILL' 0,
65
+ 'wght' 400,
66
+ 'GRAD' 0,
67
+ 'opsz' 24;
68
+ letter-spacing: normal;
69
+ line-height: 1;
70
+ text-transform: none;
71
+ white-space: nowrap;
72
+ word-wrap: normal;
73
+ direction: ltr;
74
+ vertical-align: middle;
68
75
  }
69
76
 
70
77
  .no-scrollbar {
71
- -ms-overflow-style: none;
72
- scrollbar-width: none;
78
+ -ms-overflow-style: none;
79
+ scrollbar-width: none;
73
80
  }
74
81
 
75
82
  .no-scrollbar::-webkit-scrollbar {
76
- display: none;
77
- height: 0;
78
- width: 0;
83
+ display: none;
84
+ height: 0;
85
+ width: 0;
79
86
  }
80
87
 
81
88
  .custom-scrollbar {
82
- scrollbar-color: var(--color-surface-highest) var(--color-surface-lowest);
83
- scrollbar-width: thin;
89
+ scrollbar-color: var(--color-surface-highest) var(--color-surface-lowest);
90
+ scrollbar-width: thin;
84
91
  }
85
92
 
86
93
  .custom-scrollbar::-webkit-scrollbar {
87
- height: var(--scrollbar-size);
88
- width: var(--scrollbar-size);
94
+ height: var(--scrollbar-size);
95
+ width: var(--scrollbar-size);
89
96
  }
90
97
 
91
98
  .custom-scrollbar::-webkit-scrollbar-track {
92
- background: var(--color-surface-lowest);
99
+ background: var(--color-surface-lowest);
93
100
  }
94
101
 
95
102
  .custom-scrollbar::-webkit-scrollbar-thumb {
96
- background: var(--color-surface-highest);
103
+ background: var(--color-surface-highest);
97
104
  }
98
105
 
99
106
  .custom-scrollbar::-webkit-scrollbar-thumb:hover {
100
- background: var(--color-primary-container);
107
+ background: var(--color-primary-container);
101
108
  }
102
109
 
103
110
  .clipped-btn,
@@ -105,72 +112,79 @@ img {
105
112
  .custom-clip,
106
113
  .clip-button,
107
114
  .clip-corner {
108
- clip-path: var(--clip-path);
115
+ clip-path: var(--clip-path);
109
116
  }
110
117
 
111
118
  .sql-keyword {
112
- color: var(--color-primary-container);
119
+ color: var(--color-primary-container);
113
120
  }
114
121
 
115
122
  .sql-string {
116
- color: var(--color-on-surface);
123
+ color: var(--color-on-surface);
117
124
  }
118
125
 
119
126
  .sql-comment {
120
- color: var(--color-outline-variant);
127
+ color: var(--color-outline-variant);
121
128
  }
122
129
 
123
130
  .sql-value {
124
- color: var(--color-on-surface-variant);
131
+ color: var(--color-on-surface-variant);
125
132
  }
126
133
 
127
134
  .query-editor-layer {
128
- min-height: 140px;
135
+ display: grid;
136
+ min-height: 140px;
137
+ overflow: hidden;
129
138
  }
130
139
 
131
140
  .query-editor-highlight,
132
141
  .query-editor-input {
133
- font: inherit;
134
- line-height: inherit;
135
- margin: 0;
136
- padding: 0;
137
- tab-size: 2;
138
- white-space: pre-wrap;
139
- word-break: break-word;
142
+ display: block;
143
+ font: inherit;
144
+ grid-area: 1 / 1;
145
+ line-height: inherit;
146
+ margin: 0;
147
+ min-height: 100%;
148
+ padding: 0;
149
+ tab-size: 2;
150
+ white-space: pre-wrap;
151
+ width: 100%;
152
+ word-break: normal;
153
+ overflow-wrap: anywhere;
140
154
  }
141
155
 
142
156
  .query-editor-highlight {
143
- color: var(--color-on-surface);
144
- inset: 0;
145
- overflow: hidden;
146
- pointer-events: none;
147
- position: absolute;
157
+ color: var(--color-on-surface);
158
+ overflow: hidden;
159
+ pointer-events: none;
148
160
  }
149
161
 
150
162
  .query-editor-input {
151
- -webkit-text-fill-color: transparent;
152
- background: transparent;
153
- caret-color: var(--color-on-surface);
154
- color: transparent;
155
- overflow: auto;
163
+ -webkit-appearance: none;
164
+ -webkit-text-fill-color: transparent;
165
+ appearance: none;
166
+ background: transparent;
167
+ caret-color: var(--color-on-surface);
168
+ color: transparent;
169
+ overflow: auto;
156
170
  }
157
171
 
158
172
  .query-editor-input::placeholder {
159
- color: transparent;
173
+ color: transparent;
160
174
  }
161
175
 
162
176
  .cursor-blink {
163
- animation: cursor-blink var(--duration-cursor-blink) step-end infinite;
164
- border-right: 2px solid var(--color-primary-container);
177
+ animation: cursor-blink var(--duration-cursor-blink) step-end infinite;
178
+ border-right: 2px solid var(--color-primary-container);
165
179
  }
166
180
 
167
181
  @keyframes cursor-blink {
168
- from,
169
- to {
170
- border-color: transparent;
171
- }
172
-
173
- 50% {
174
- border-color: var(--color-primary-container);
175
- }
182
+ from,
183
+ to {
184
+ border-color: transparent;
185
+ }
186
+
187
+ 50% {
188
+ border-color: var(--color-primary-container);
189
+ }
176
190
  }