sqlite-hub 0.17.2 → 1.0.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.
- package/README.md +101 -19
- package/bin/sqlite-hub.js +83 -401
- package/examples/api/queries.js +31 -0
- package/examples/api/rows.js +27 -0
- package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
- package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
- package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
- package/frontend/assets/mockups/connections_1200.webp +0 -0
- package/frontend/assets/mockups/data_1_1200.webp +0 -0
- package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
- package/frontend/assets/mockups/documents_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
- package/frontend/assets/mockups/overview_1200.webp +0 -0
- package/frontend/assets/mockups/settings_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
- package/frontend/assets/mockups/structure_1_1200.webp +0 -0
- package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
- package/frontend/js/api.js +19 -0
- package/frontend/js/app.js +64 -0
- package/frontend/js/components/formControls.js +38 -0
- package/frontend/js/components/modal.js +60 -20
- package/frontend/js/store.js +115 -0
- package/frontend/js/views/settings.js +141 -5
- package/frontend/styles/tailwind.generated.css +2 -2
- package/package.json +6 -6
- package/server/middleware/apiTokenAuth.js +30 -0
- package/server/routes/connections.js +17 -0
- package/server/routes/externalApi.js +222 -0
- package/server/routes/settings.js +64 -4
- package/server/server.js +22 -1
- package/server/services/apiTokenService.js +101 -0
- package/server/services/databaseCommandService.js +399 -0
- package/server/services/nativeFileDialogService.js +93 -1
- package/server/services/storage/appStateStore.js +113 -0
- package/server/utils/errors.js +7 -0
- package/tests/api-token-auth.test.js +127 -0
- package/tests/cli-service-delegation.test.js +43 -0
- package/tests/connections-file-dialog-route.test.js +43 -0
- package/tests/copy-column-modal.test.js +34 -0
- package/tests/database-command-service.test.js +102 -0
- package/tests/form-controls.test.js +34 -0
- package/tests/native-file-dialog.test.js +27 -0
- package/tests/settings-api-tokens-route.test.js +76 -0
- package/tests/settings-view.test.js +47 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
package/bin/sqlite-hub.js
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { spawn } = require('node:child_process');
|
|
6
|
+
const {
|
|
7
|
+
DatabaseCommandService,
|
|
8
|
+
getQueryTitle,
|
|
9
|
+
} = require('../server/services/databaseCommandService');
|
|
6
10
|
|
|
7
11
|
const DEFAULT_PORT = 4173;
|
|
8
12
|
const EXPORT_FORMATS = new Set(['csv', 'tsv', 'md']);
|
|
@@ -421,14 +425,6 @@ function openInDefaultBrowser(url) {
|
|
|
421
425
|
child.unref();
|
|
422
426
|
}
|
|
423
427
|
|
|
424
|
-
function findDatabaseByName(connections, name) {
|
|
425
|
-
const normalizedName = String(name ?? '').toLowerCase();
|
|
426
|
-
|
|
427
|
-
return connections.find(
|
|
428
|
-
conn => conn.label.toLowerCase() === normalizedName || conn.id.toLowerCase() === normalizedName,
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
428
|
function formatSize(bytes) {
|
|
433
429
|
if (!bytes) return 'N/A';
|
|
434
430
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -436,20 +432,6 @@ function formatSize(bytes) {
|
|
|
436
432
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
437
433
|
}
|
|
438
434
|
|
|
439
|
-
function sanitizeFilenameBase(value, fallback = 'export') {
|
|
440
|
-
const sanitized = String(value ?? '')
|
|
441
|
-
.replace(/[<>:"/\\|?*\u0000-\u001f]/g, ' ')
|
|
442
|
-
.replace(/\s+/g, ' ')
|
|
443
|
-
.trim()
|
|
444
|
-
.replace(/[. ]+$/g, '');
|
|
445
|
-
|
|
446
|
-
if (!sanitized) {
|
|
447
|
-
return fallback;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return sanitized.slice(0, 120);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
435
|
function createAppStateStore() {
|
|
454
436
|
const { resolveAppStatePaths } = require('../server/utils/appPaths');
|
|
455
437
|
const { AppStateStore } = require('../server/services/storage/appStateStore');
|
|
@@ -459,39 +441,6 @@ function createAppStateStore() {
|
|
|
459
441
|
return new AppStateStore(appStateDbPath);
|
|
460
442
|
}
|
|
461
443
|
|
|
462
|
-
function createReadOnlyRuntime(conn, appStateStore) {
|
|
463
|
-
const { ConnectionManager } = require('../server/services/sqlite/connectionManager');
|
|
464
|
-
const { DataBrowserService } = require('../server/services/sqlite/dataBrowserService');
|
|
465
|
-
const { ExportService } = require('../server/services/sqlite/exportService');
|
|
466
|
-
const { SqlExecutor } = require('../server/services/sqlite/sqlExecutor');
|
|
467
|
-
|
|
468
|
-
const connectionManager = new ConnectionManager({ appStateStore });
|
|
469
|
-
|
|
470
|
-
connectionManager.openConnection({
|
|
471
|
-
filePath: conn.path,
|
|
472
|
-
label: conn.label,
|
|
473
|
-
id: conn.id,
|
|
474
|
-
logoPath: conn.logoPath ?? null,
|
|
475
|
-
makeActive: false,
|
|
476
|
-
readOnly: true,
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
const sqlExecutor = new SqlExecutor({ connectionManager, appStateStore });
|
|
480
|
-
const exportService = new ExportService({ appStateStore, connectionManager, sqlExecutor });
|
|
481
|
-
const dataBrowserService = new DataBrowserService({ connectionManager });
|
|
482
|
-
|
|
483
|
-
return {
|
|
484
|
-
connectionManager,
|
|
485
|
-
db: connectionManager.getActiveDatabase(),
|
|
486
|
-
dataBrowserService,
|
|
487
|
-
exportService,
|
|
488
|
-
sqlExecutor,
|
|
489
|
-
close() {
|
|
490
|
-
connectionManager.closeCurrent();
|
|
491
|
-
},
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
|
|
495
444
|
function printDatabaseList(connections) {
|
|
496
445
|
if (connections.length === 0) {
|
|
497
446
|
console.log('No databases imported yet.');
|
|
@@ -538,72 +487,8 @@ function printTables(conn, tables) {
|
|
|
538
487
|
console.log('');
|
|
539
488
|
}
|
|
540
489
|
|
|
541
|
-
function
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function findQueryByName(appStateStore, conn, queryName) {
|
|
546
|
-
const normalizedQueryName = String(queryName ?? '').trim().toLowerCase();
|
|
547
|
-
|
|
548
|
-
if (!normalizedQueryName) {
|
|
549
|
-
throw new Error('Query name is required.');
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const collection = appStateStore.buildQueryHistoryCollection({
|
|
553
|
-
databaseKey: conn.id,
|
|
554
|
-
search: queryName,
|
|
555
|
-
onlySaved: false,
|
|
556
|
-
limit: 100,
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
return (
|
|
560
|
-
collection.items.find(item => {
|
|
561
|
-
const candidates = [item.id, item.title, item.displayTitle].filter(Boolean);
|
|
562
|
-
|
|
563
|
-
return candidates.some(candidate => String(candidate).toLowerCase() === normalizedQueryName);
|
|
564
|
-
}) ||
|
|
565
|
-
collection.items.find(item => String(item.rawSql ?? '').toLowerCase().includes(normalizedQueryName)) ||
|
|
566
|
-
null
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
function getSavedQueries(appStateStore, conn, limit = 100) {
|
|
571
|
-
return appStateStore.buildQueryHistoryCollection({
|
|
572
|
-
databaseKey: conn.id,
|
|
573
|
-
onlySaved: true,
|
|
574
|
-
limit,
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function printAvailableQueries(appStateStore, conn) {
|
|
579
|
-
const allQueries = getSavedQueries(appStateStore, conn);
|
|
580
|
-
|
|
581
|
-
console.error('\nAvailable saved queries:');
|
|
582
|
-
|
|
583
|
-
if (allQueries.items.length > 0) {
|
|
584
|
-
allQueries.items.forEach(query => {
|
|
585
|
-
console.error(` - ${getQueryTitle(query)}`);
|
|
586
|
-
});
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
console.error(' (none)');
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function requireMatchingQuery(appStateStore, conn, queryName) {
|
|
594
|
-
const matchingQuery = findQueryByName(appStateStore, conn, queryName);
|
|
595
|
-
|
|
596
|
-
if (!matchingQuery) {
|
|
597
|
-
console.error(`Saved query not found: ${queryName}`);
|
|
598
|
-
printAvailableQueries(appStateStore, conn);
|
|
599
|
-
process.exit(1);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return matchingQuery;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function listSavedQueries(appStateStore, conn) {
|
|
606
|
-
const savedQueries = getSavedQueries(appStateStore, conn);
|
|
490
|
+
function listSavedQueries(databaseService, conn) {
|
|
491
|
+
const savedQueries = databaseService.listSavedQueries(conn.id);
|
|
607
492
|
|
|
608
493
|
if (savedQueries.items.length === 0) {
|
|
609
494
|
console.log(`No saved queries found for ${conn.label}.`);
|
|
@@ -664,28 +549,24 @@ function printExecutionResult(result) {
|
|
|
664
549
|
});
|
|
665
550
|
}
|
|
666
551
|
|
|
667
|
-
function executeSavedQuery({
|
|
668
|
-
const matchingQuery =
|
|
552
|
+
function executeSavedQuery({ databaseService, conn, queryName }) {
|
|
553
|
+
const { query: matchingQuery, result } = databaseService.executeSavedQuery(conn.id, queryName);
|
|
669
554
|
|
|
670
555
|
console.log(`\nExecuting: ${getQueryTitle(matchingQuery)}`);
|
|
671
556
|
console.log(`SQL: ${matchingQuery.previewSql || matchingQuery.rawSql}`);
|
|
672
557
|
console.log('─'.repeat(60));
|
|
673
558
|
|
|
674
|
-
const result = sqlExecutor.execute(matchingQuery.rawSql, {
|
|
675
|
-
persistHistory: false,
|
|
676
|
-
});
|
|
677
|
-
|
|
678
559
|
printExecutionResult(result);
|
|
679
560
|
}
|
|
680
561
|
|
|
681
|
-
function showSavedQuery({
|
|
682
|
-
const matchingQuery =
|
|
562
|
+
function showSavedQuery({ databaseService, conn, queryName }) {
|
|
563
|
+
const matchingQuery = databaseService.getSavedQuery(conn.id, queryName);
|
|
683
564
|
|
|
684
565
|
console.log(matchingQuery.rawSql);
|
|
685
566
|
}
|
|
686
567
|
|
|
687
|
-
function showSavedQueryNotes({
|
|
688
|
-
const matchingQuery =
|
|
568
|
+
function showSavedQueryNotes({ databaseService, conn, queryName }) {
|
|
569
|
+
const matchingQuery = databaseService.getSavedQuery(conn.id, queryName);
|
|
689
570
|
const notes = String(matchingQuery.notes ?? '').trim();
|
|
690
571
|
|
|
691
572
|
if (notes) {
|
|
@@ -696,9 +577,8 @@ function showSavedQueryNotes({ appStateStore, conn, queryName }) {
|
|
|
696
577
|
console.log(`No notes saved for: ${getQueryTitle(matchingQuery)}`);
|
|
697
578
|
}
|
|
698
579
|
|
|
699
|
-
function exportSavedQuery({
|
|
700
|
-
const matchingQuery =
|
|
701
|
-
const result = exportService.exportQuery(matchingQuery.rawSql, { format });
|
|
580
|
+
function exportSavedQuery({ databaseService, conn, queryName, format }) {
|
|
581
|
+
const { query: matchingQuery, result } = databaseService.exportSavedQuery(conn.id, queryName, format);
|
|
702
582
|
const outputPath = path.resolve(process.cwd(), result.filename);
|
|
703
583
|
|
|
704
584
|
fs.writeFileSync(outputPath, result.content, 'utf8');
|
|
@@ -709,66 +589,8 @@ function exportSavedQuery({ appStateStore, conn, exportService, queryName, forma
|
|
|
709
589
|
console.log(`File: ${outputPath}`);
|
|
710
590
|
}
|
|
711
591
|
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function printAvailableDocuments(appStateStore, conn) {
|
|
717
|
-
const documents = appStateStore.listDatabaseDocuments(conn.id);
|
|
718
|
-
|
|
719
|
-
console.error('\nAvailable documents:');
|
|
720
|
-
|
|
721
|
-
if (documents.length > 0) {
|
|
722
|
-
documents.forEach(document => {
|
|
723
|
-
console.error(` - ${getDocumentTitle(document)}`);
|
|
724
|
-
});
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
console.error(' (none)');
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
function findDocumentByName(appStateStore, conn, documentName) {
|
|
732
|
-
const normalizedDocumentName = String(documentName ?? '').trim().toLowerCase();
|
|
733
|
-
|
|
734
|
-
if (!normalizedDocumentName) {
|
|
735
|
-
throw new Error('Document name is required.');
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
const documents = appStateStore.listDatabaseDocuments(conn.id);
|
|
739
|
-
const exactMatch = documents.find(document => {
|
|
740
|
-
const candidates = [document.id, document.filename, document.title].filter(Boolean);
|
|
741
|
-
|
|
742
|
-
return candidates.some(candidate => String(candidate).toLowerCase() === normalizedDocumentName);
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
if (exactMatch) {
|
|
746
|
-
return appStateStore.getDatabaseDocument(conn.id, exactMatch.id);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
const partialMatch = documents.find(document => {
|
|
750
|
-
const candidates = [document.filename, document.title].filter(Boolean);
|
|
751
|
-
|
|
752
|
-
return candidates.some(candidate => String(candidate).toLowerCase().includes(normalizedDocumentName));
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
return partialMatch ? appStateStore.getDatabaseDocument(conn.id, partialMatch.id) : null;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function requireMatchingDocument(appStateStore, conn, documentName) {
|
|
759
|
-
const matchingDocument = findDocumentByName(appStateStore, conn, documentName);
|
|
760
|
-
|
|
761
|
-
if (!matchingDocument) {
|
|
762
|
-
console.error(`Document not found: ${documentName}`);
|
|
763
|
-
printAvailableDocuments(appStateStore, conn);
|
|
764
|
-
process.exit(1);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
return matchingDocument;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function listDocuments(appStateStore, conn) {
|
|
771
|
-
const documents = appStateStore.listDatabaseDocuments(conn.id);
|
|
592
|
+
function listDocuments(databaseService, conn) {
|
|
593
|
+
const documents = databaseService.listDocuments(conn.id);
|
|
772
594
|
|
|
773
595
|
if (documents.length === 0) {
|
|
774
596
|
console.log(`No documents found for ${conn.label}.`);
|
|
@@ -787,156 +609,27 @@ function listDocuments(appStateStore, conn) {
|
|
|
787
609
|
console.log('');
|
|
788
610
|
}
|
|
789
611
|
|
|
790
|
-
function showDocumentMarkdown({
|
|
791
|
-
const matchingDocument =
|
|
612
|
+
function showDocumentMarkdown({ databaseService, conn, documentName }) {
|
|
613
|
+
const matchingDocument = databaseService.getDocument(conn.id, documentName);
|
|
792
614
|
|
|
793
615
|
console.log(matchingDocument.content ?? '');
|
|
794
616
|
}
|
|
795
617
|
|
|
796
|
-
function
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
.replace(/[<>:"/\\|?*]+/g, ' ')
|
|
800
|
-
.replace(/\s+/g, ' ')
|
|
801
|
-
.trim()
|
|
802
|
-
.replace(/^\.+/, '')
|
|
803
|
-
.replace(/[. ]+$/g, '');
|
|
804
|
-
|
|
805
|
-
if (!normalizedFilename) {
|
|
806
|
-
normalizedFilename = fallback;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
if (!/\.md$/i.test(normalizedFilename)) {
|
|
810
|
-
normalizedFilename = `${normalizedFilename}.md`;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
if (normalizedFilename.length > 160) {
|
|
814
|
-
normalizedFilename = `${normalizedFilename.slice(0, 157)}.md`;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
return normalizedFilename;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
function exportDocumentMarkdown({ appStateStore, conn, documentName }) {
|
|
821
|
-
const matchingDocument = requireMatchingDocument(appStateStore, conn, documentName);
|
|
822
|
-
const filename = normalizeMarkdownExportFilename(matchingDocument.filename, `${matchingDocument.title || 'document'}.md`);
|
|
823
|
-
const outputPath = path.resolve(process.cwd(), filename);
|
|
618
|
+
function exportDocumentMarkdown({ databaseService, conn, documentName }) {
|
|
619
|
+
const result = databaseService.exportDocument(conn.id, documentName);
|
|
620
|
+
const outputPath = path.resolve(process.cwd(), result.filename);
|
|
824
621
|
|
|
825
|
-
fs.writeFileSync(outputPath,
|
|
622
|
+
fs.writeFileSync(outputPath, result.content, 'utf8');
|
|
826
623
|
|
|
827
|
-
console.log(`Exported document: ${
|
|
828
|
-
console.log(`Characters: ${
|
|
624
|
+
console.log(`Exported document: ${result.document.filename}`);
|
|
625
|
+
console.log(`Characters: ${result.document.contentLength}`);
|
|
829
626
|
console.log(`File: ${outputPath}`);
|
|
830
627
|
}
|
|
628
|
+
function exportTableRowAsJson({ databaseService, conn, tableName, exportTarget }) {
|
|
629
|
+
const result = databaseService.getTableRow(conn.id, tableName, exportTarget);
|
|
630
|
+
const outputPath = path.resolve(process.cwd(), result.filename);
|
|
831
631
|
|
|
832
|
-
|
|
833
|
-
const text = String(value ?? '');
|
|
834
|
-
const affinity = String(column?.affinity ?? '').toUpperCase();
|
|
835
|
-
|
|
836
|
-
if ((affinity === 'INTEGER' || affinity === 'REAL' || affinity === 'NUMERIC') && text.trim() !== '') {
|
|
837
|
-
const numberValue = Number(text);
|
|
838
|
-
|
|
839
|
-
if (Number.isFinite(numberValue)) {
|
|
840
|
-
return numberValue;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
return value;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
function parseCompositePrimaryKeyValue(rawValue) {
|
|
848
|
-
try {
|
|
849
|
-
const parsed = JSON.parse(rawValue);
|
|
850
|
-
|
|
851
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
852
|
-
return parsed;
|
|
853
|
-
}
|
|
854
|
-
} catch (error) {
|
|
855
|
-
// Fall through to the clearer error below.
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
throw new Error('Composite primary key export requires a JSON object, for example --export:\'{"id":1,"locale":"en"}\'.');
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
function buildIdentityFromExportTarget(tableDetail, exportTarget) {
|
|
862
|
-
if (tableDetail.identityStrategy?.type === 'rowid') {
|
|
863
|
-
const numberValue = Number(exportTarget);
|
|
864
|
-
const rowid = Number.isInteger(numberValue) ? numberValue : exportTarget;
|
|
865
|
-
|
|
866
|
-
return {
|
|
867
|
-
kind: 'rowid',
|
|
868
|
-
values: {
|
|
869
|
-
rowid,
|
|
870
|
-
},
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
if (tableDetail.identityStrategy?.type === 'primaryKey') {
|
|
875
|
-
const columns = tableDetail.identityStrategy.columns ?? [];
|
|
876
|
-
|
|
877
|
-
if (columns.length === 1) {
|
|
878
|
-
const columnName = columns[0];
|
|
879
|
-
const column = tableDetail.columns.find(candidate => candidate.name === columnName);
|
|
880
|
-
|
|
881
|
-
return {
|
|
882
|
-
kind: 'primaryKey',
|
|
883
|
-
columns,
|
|
884
|
-
values: {
|
|
885
|
-
[columnName]: coerceIdentityValue(column, exportTarget),
|
|
886
|
-
},
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
const parsed = parseCompositePrimaryKeyValue(exportTarget);
|
|
891
|
-
|
|
892
|
-
return {
|
|
893
|
-
kind: 'primaryKey',
|
|
894
|
-
columns,
|
|
895
|
-
values: Object.fromEntries(
|
|
896
|
-
columns.map(columnName => {
|
|
897
|
-
if (!Object.prototype.hasOwnProperty.call(parsed, columnName)) {
|
|
898
|
-
throw new Error(`Missing primary key value for ${columnName}.`);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
const column = tableDetail.columns.find(candidate => candidate.name === columnName);
|
|
902
|
-
|
|
903
|
-
return [columnName, coerceIdentityValue(column, parsed[columnName])];
|
|
904
|
-
}),
|
|
905
|
-
),
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
throw new Error(`Table ${tableDetail.name} has no stable row identity.`);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function buildDataRowEditorJsonObject({ row, columns = [] } = {}) {
|
|
913
|
-
const names = columns
|
|
914
|
-
.map(column => String(typeof column === 'object' ? column?.name : column ?? '').trim())
|
|
915
|
-
.filter(name => name && name !== '__identity');
|
|
916
|
-
const sourceNames = names.length
|
|
917
|
-
? names
|
|
918
|
-
: Object.keys(row ?? {}).filter(name => name !== '__identity');
|
|
919
|
-
|
|
920
|
-
return Object.fromEntries(
|
|
921
|
-
sourceNames
|
|
922
|
-
.map(name => [name, Object.prototype.hasOwnProperty.call(row ?? {}, name) ? row[name] : undefined])
|
|
923
|
-
.filter(([, value]) => value !== undefined),
|
|
924
|
-
);
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function exportTableRowAsJson({ dataBrowserService, db, tableName, exportTarget }) {
|
|
928
|
-
const { getTableDetail } = require('../server/services/sqlite/introspection');
|
|
929
|
-
const tableDetail = getTableDetail(db, tableName, { includeRowCount: false });
|
|
930
|
-
const identity = buildIdentityFromExportTarget(tableDetail, exportTarget);
|
|
931
|
-
const { row } = dataBrowserService.getTableRow(tableName, { identity });
|
|
932
|
-
const rowObject = buildDataRowEditorJsonObject({
|
|
933
|
-
row,
|
|
934
|
-
columns: tableDetail.columns.filter(column => column.visible),
|
|
935
|
-
});
|
|
936
|
-
const filenameBase = sanitizeFilenameBase(`${tableName}-${exportTarget}`, `${tableName}-row`);
|
|
937
|
-
const outputPath = path.resolve(process.cwd(), `${filenameBase}.json`);
|
|
938
|
-
|
|
939
|
-
fs.writeFileSync(outputPath, `${JSON.stringify(rowObject, null, 2)}\n`, 'utf8');
|
|
632
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(result.data, null, 2)}\n`, 'utf8');
|
|
940
633
|
|
|
941
634
|
console.log(`Exported row: ${tableName}`);
|
|
942
635
|
console.log(`Key: ${exportTarget}`);
|
|
@@ -1049,8 +742,8 @@ function requireDatabaseName(options) {
|
|
|
1049
742
|
return options.databaseName;
|
|
1050
743
|
}
|
|
1051
744
|
|
|
1052
|
-
async function main() {
|
|
1053
|
-
const options = parseCliArguments(
|
|
745
|
+
async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
746
|
+
const options = parseCliArguments(argv);
|
|
1054
747
|
const port = options.port ?? DEFAULT_PORT;
|
|
1055
748
|
|
|
1056
749
|
if (options.help) {
|
|
@@ -1074,8 +767,12 @@ async function main() {
|
|
|
1074
767
|
return;
|
|
1075
768
|
}
|
|
1076
769
|
|
|
1077
|
-
const
|
|
1078
|
-
|
|
770
|
+
const databaseService =
|
|
771
|
+
dependencies.databaseService ??
|
|
772
|
+
new DatabaseCommandService({
|
|
773
|
+
appStateStore: dependencies.appStateStore ?? createAppStateStore(),
|
|
774
|
+
});
|
|
775
|
+
const connections = databaseService.listDatabases();
|
|
1079
776
|
|
|
1080
777
|
if (options.databaseList && !options.databaseName && !hasDatabaseOperation(options)) {
|
|
1081
778
|
printDatabaseList(connections);
|
|
@@ -1084,24 +781,19 @@ async function main() {
|
|
|
1084
781
|
|
|
1085
782
|
if (options.databaseName || hasDatabaseOperation(options)) {
|
|
1086
783
|
const dbName = requireDatabaseName(options);
|
|
1087
|
-
const conn =
|
|
1088
|
-
|
|
1089
|
-
if (!conn) {
|
|
1090
|
-
console.error(`Database not found: ${dbName}`);
|
|
1091
|
-
process.exit(1);
|
|
1092
|
-
}
|
|
784
|
+
const conn = databaseService.getDatabase(dbName);
|
|
1093
785
|
|
|
1094
786
|
if (options.documents) {
|
|
1095
787
|
if (options.documentName) {
|
|
1096
788
|
if (options.documentExport) {
|
|
1097
789
|
exportDocumentMarkdown({
|
|
1098
|
-
|
|
790
|
+
databaseService,
|
|
1099
791
|
conn,
|
|
1100
792
|
documentName: options.documentName,
|
|
1101
793
|
});
|
|
1102
794
|
} else {
|
|
1103
795
|
showDocumentMarkdown({
|
|
1104
|
-
|
|
796
|
+
databaseService,
|
|
1105
797
|
conn,
|
|
1106
798
|
documentName: options.documentName,
|
|
1107
799
|
});
|
|
@@ -1109,73 +801,63 @@ async function main() {
|
|
|
1109
801
|
return;
|
|
1110
802
|
}
|
|
1111
803
|
|
|
1112
|
-
listDocuments(
|
|
804
|
+
listDocuments(databaseService, conn);
|
|
1113
805
|
return;
|
|
1114
806
|
}
|
|
1115
807
|
|
|
1116
808
|
if (options.tableName || options.tables || options.queries || options.executeQuery || options.showQuery || options.showNotes || options.exportTarget) {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
try {
|
|
1120
|
-
if (options.tableName) {
|
|
1121
|
-
const { getTableDetail } = require('../server/services/sqlite/introspection');
|
|
1122
|
-
|
|
1123
|
-
if (options.exportTarget) {
|
|
1124
|
-
exportTableRowAsJson({
|
|
1125
|
-
dataBrowserService: runtime.dataBrowserService,
|
|
1126
|
-
db: runtime.db,
|
|
1127
|
-
tableName: options.tableName,
|
|
1128
|
-
exportTarget: options.exportTarget,
|
|
1129
|
-
});
|
|
1130
|
-
} else {
|
|
1131
|
-
printTableInfo(getTableDetail(runtime.db, options.tableName));
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
809
|
+
if (options.tableName) {
|
|
1137
810
|
if (options.exportTarget) {
|
|
1138
|
-
|
|
1139
|
-
|
|
811
|
+
exportTableRowAsJson({
|
|
812
|
+
databaseService,
|
|
1140
813
|
conn,
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
format: options.exportFormat,
|
|
814
|
+
tableName: options.tableName,
|
|
815
|
+
exportTarget: options.exportTarget,
|
|
1144
816
|
});
|
|
1145
|
-
|
|
817
|
+
} else {
|
|
818
|
+
printTableInfo(databaseService.getTable(conn.id, options.tableName));
|
|
1146
819
|
}
|
|
1147
820
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
appStateStore,
|
|
1151
|
-
conn,
|
|
1152
|
-
sqlExecutor: runtime.sqlExecutor,
|
|
1153
|
-
queryName: options.executeQuery,
|
|
1154
|
-
});
|
|
1155
|
-
return;
|
|
1156
|
-
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
1157
823
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
824
|
+
if (options.exportTarget) {
|
|
825
|
+
exportSavedQuery({
|
|
826
|
+
databaseService,
|
|
827
|
+
conn,
|
|
828
|
+
queryName: options.exportTarget,
|
|
829
|
+
format: options.exportFormat,
|
|
830
|
+
});
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
1162
833
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
834
|
+
if (options.executeQuery) {
|
|
835
|
+
executeSavedQuery({
|
|
836
|
+
databaseService,
|
|
837
|
+
conn,
|
|
838
|
+
queryName: options.executeQuery,
|
|
839
|
+
});
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
1167
842
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
843
|
+
if (options.showQuery) {
|
|
844
|
+
showSavedQuery({ databaseService, conn, queryName: options.showQuery });
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
1172
847
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
848
|
+
if (options.showNotes) {
|
|
849
|
+
showSavedQueryNotes({ databaseService, conn, queryName: options.showNotes });
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (options.queries) {
|
|
854
|
+
listSavedQueries(databaseService, conn);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (options.tables) {
|
|
859
|
+
printTables(conn, databaseService.listTables(conn.id));
|
|
860
|
+
return;
|
|
1179
861
|
}
|
|
1180
862
|
}
|
|
1181
863
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const SQLITE_HUB_URL = 'http://127.0.0.1:4180';
|
|
2
|
+
// Create a new token for your database in Settings > API Tokens.
|
|
3
|
+
const apiToken = 'shub_Lv9A2xocv01PqT6a0jEmuT-PxPyqfzI_UBaY_VEzjqE';
|
|
4
|
+
const DATABASE_ID = 'conn_ae9b5e54ae8eca1d';
|
|
5
|
+
const path = `${SQLITE_HUB_URL}/api/v1/databases/${DATABASE_ID}`;
|
|
6
|
+
|
|
7
|
+
async function databaseInfo(path, { method = 'GET' } = {}) {
|
|
8
|
+
console.log(path);
|
|
9
|
+
const response = await fetch(path, {
|
|
10
|
+
method,
|
|
11
|
+
headers: {
|
|
12
|
+
Accept: 'application/json',
|
|
13
|
+
Authorization: `Bearer ${apiToken}`,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const queries = await databaseInfo(`${path}/queries`);
|
|
20
|
+
|
|
21
|
+
console.log(queries.data.items.map(query => query.displayTitle));
|
|
22
|
+
|
|
23
|
+
const queryName = encodeURIComponent(queries.data.items[0].displayTitle);
|
|
24
|
+
const query = await databaseInfo(`${path}/queries/${queryName}`);
|
|
25
|
+
|
|
26
|
+
console.log(query.data);
|
|
27
|
+
|
|
28
|
+
// Executing a saved query changes server state and therefore requires POST.
|
|
29
|
+
const exec = await databaseInfo(`${path}/queries/${queryName}/execute`, { method: 'POST' });
|
|
30
|
+
|
|
31
|
+
console.log(exec.data);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const SQLITE_HUB_URL = 'http://127.0.0.1:4180';
|
|
2
|
+
// Create a new token for your database in Settings > API Tokens.
|
|
3
|
+
const apiToken = 'shub_Lv9A2xocv01PqT6a0jEmuT-PxPyqfzI_UBaY_VEzjqE';
|
|
4
|
+
const DATABASE_ID = 'conn_ae9b5e54ae8eca1d';
|
|
5
|
+
const path = `${SQLITE_HUB_URL}/api/v1/databases/${DATABASE_ID}`;
|
|
6
|
+
|
|
7
|
+
async function databaseInfo(path) {
|
|
8
|
+
console.log(path);
|
|
9
|
+
const response = await fetch(path, {
|
|
10
|
+
headers: {
|
|
11
|
+
Accept: 'application/json',
|
|
12
|
+
Authorization: `Bearer ${apiToken}`,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const info = await databaseInfo(path);
|
|
20
|
+
const tables = await databaseInfo(`${path}/tables`);
|
|
21
|
+
|
|
22
|
+
console.log(info.data);
|
|
23
|
+
console.log(tables.data.items);
|
|
24
|
+
|
|
25
|
+
const row = await databaseInfo(`${path}/tables/${tables.data.items[0].name}`);
|
|
26
|
+
|
|
27
|
+
console.log(row.data);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|