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.
Files changed (61) hide show
  1. package/README.md +101 -19
  2. package/bin/sqlite-hub.js +83 -401
  3. package/examples/api/queries.js +31 -0
  4. package/examples/api/rows.js +27 -0
  5. package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
  6. package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
  7. package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
  8. package/frontend/assets/mockups/connections_1200.webp +0 -0
  9. package/frontend/assets/mockups/data_1_1200.webp +0 -0
  10. package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
  11. package/frontend/assets/mockups/documents_1200.webp +0 -0
  12. package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
  13. package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
  14. package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
  15. package/frontend/assets/mockups/overview_1200.webp +0 -0
  16. package/frontend/assets/mockups/settings_1200.webp +0 -0
  17. package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
  18. package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
  19. package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
  20. package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
  21. package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
  22. package/frontend/assets/mockups/structure_1_1200.webp +0 -0
  23. package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
  24. package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
  25. package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
  26. package/frontend/js/api.js +19 -0
  27. package/frontend/js/app.js +64 -0
  28. package/frontend/js/components/formControls.js +38 -0
  29. package/frontend/js/components/modal.js +60 -20
  30. package/frontend/js/store.js +115 -0
  31. package/frontend/js/views/settings.js +141 -5
  32. package/frontend/styles/tailwind.generated.css +2 -2
  33. package/package.json +6 -6
  34. package/server/middleware/apiTokenAuth.js +30 -0
  35. package/server/routes/connections.js +17 -0
  36. package/server/routes/externalApi.js +222 -0
  37. package/server/routes/settings.js +64 -4
  38. package/server/server.js +22 -1
  39. package/server/services/apiTokenService.js +101 -0
  40. package/server/services/databaseCommandService.js +399 -0
  41. package/server/services/nativeFileDialogService.js +93 -1
  42. package/server/services/storage/appStateStore.js +113 -0
  43. package/server/utils/errors.js +7 -0
  44. package/tests/api-token-auth.test.js +127 -0
  45. package/tests/cli-service-delegation.test.js +43 -0
  46. package/tests/connections-file-dialog-route.test.js +43 -0
  47. package/tests/copy-column-modal.test.js +34 -0
  48. package/tests/database-command-service.test.js +102 -0
  49. package/tests/form-controls.test.js +34 -0
  50. package/tests/native-file-dialog.test.js +27 -0
  51. package/tests/settings-api-tokens-route.test.js +76 -0
  52. package/tests/settings-view.test.js +47 -0
  53. package/frontend/assets/mockups/connections.png +0 -0
  54. package/frontend/assets/mockups/data.png +0 -0
  55. package/frontend/assets/mockups/data_row_editor.png +0 -0
  56. package/frontend/assets/mockups/home.png +0 -0
  57. package/frontend/assets/mockups/sql_editor.png +0 -0
  58. package/frontend/assets/mockups/sql_editor_croped.png +0 -0
  59. package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
  60. package/frontend/assets/mockups/structure.png +0 -0
  61. 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 getQueryTitle(item) {
542
- return item?.title || item?.displayTitle || item?.previewSql || item?.rawSql || '(untitled query)';
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({ appStateStore, conn, sqlExecutor, queryName }) {
668
- const matchingQuery = requireMatchingQuery(appStateStore, conn, queryName);
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({ appStateStore, conn, queryName }) {
682
- const matchingQuery = requireMatchingQuery(appStateStore, conn, queryName);
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({ appStateStore, conn, queryName }) {
688
- const matchingQuery = requireMatchingQuery(appStateStore, conn, queryName);
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({ appStateStore, conn, exportService, queryName, format }) {
700
- const matchingQuery = requireMatchingQuery(appStateStore, conn, queryName);
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 getDocumentTitle(document) {
713
- return document?.filename || document?.title || document?.id || '(untitled document)';
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({ appStateStore, conn, documentName }) {
791
- const matchingDocument = requireMatchingDocument(appStateStore, conn, documentName);
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 normalizeMarkdownExportFilename(filename, fallback = 'document.md') {
797
- let normalizedFilename = String(filename ?? '')
798
- .replace(/[\u0000-\u001f\u007f]/g, ' ')
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, matchingDocument.content ?? '', 'utf8');
622
+ fs.writeFileSync(outputPath, result.content, 'utf8');
826
623
 
827
- console.log(`Exported document: ${matchingDocument.filename}`);
828
- console.log(`Characters: ${matchingDocument.contentLength}`);
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
- function coerceIdentityValue(column, value) {
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(process.argv.slice(2));
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 appStateStore = createAppStateStore();
1078
- const connections = appStateStore.getRecentConnections();
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 = findDatabaseByName(connections, dbName);
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
- appStateStore,
790
+ databaseService,
1099
791
  conn,
1100
792
  documentName: options.documentName,
1101
793
  });
1102
794
  } else {
1103
795
  showDocumentMarkdown({
1104
- appStateStore,
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(appStateStore, conn);
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
- const runtime = createReadOnlyRuntime(conn, appStateStore);
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
- exportSavedQuery({
1139
- appStateStore,
811
+ exportTableRowAsJson({
812
+ databaseService,
1140
813
  conn,
1141
- exportService: runtime.exportService,
1142
- queryName: options.exportTarget,
1143
- format: options.exportFormat,
814
+ tableName: options.tableName,
815
+ exportTarget: options.exportTarget,
1144
816
  });
1145
- return;
817
+ } else {
818
+ printTableInfo(databaseService.getTable(conn.id, options.tableName));
1146
819
  }
1147
820
 
1148
- if (options.executeQuery) {
1149
- executeSavedQuery({
1150
- appStateStore,
1151
- conn,
1152
- sqlExecutor: runtime.sqlExecutor,
1153
- queryName: options.executeQuery,
1154
- });
1155
- return;
1156
- }
821
+ return;
822
+ }
1157
823
 
1158
- if (options.showQuery) {
1159
- showSavedQuery({ appStateStore, conn, queryName: options.showQuery });
1160
- return;
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
- if (options.showNotes) {
1164
- showSavedQueryNotes({ appStateStore, conn, queryName: options.showNotes });
1165
- return;
1166
- }
834
+ if (options.executeQuery) {
835
+ executeSavedQuery({
836
+ databaseService,
837
+ conn,
838
+ queryName: options.executeQuery,
839
+ });
840
+ return;
841
+ }
1167
842
 
1168
- if (options.queries) {
1169
- listSavedQueries(appStateStore, conn);
1170
- return;
1171
- }
843
+ if (options.showQuery) {
844
+ showSavedQuery({ databaseService, conn, queryName: options.showQuery });
845
+ return;
846
+ }
1172
847
 
1173
- if (options.tables) {
1174
- printTables(conn, runtime.dataBrowserService.listTables());
1175
- return;
1176
- }
1177
- } finally {
1178
- runtime.close();
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);