sqlite-hub 0.10.0 → 0.12.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.
@@ -1,97 +1,97 @@
1
1
  :root {
2
- --rgb-primary: 252 227 0;
3
- --rgb-outline: 75 71 50;
4
- --rgb-on-surface: 229 226 225;
5
- --rgb-on-surface-variant: 205 199 171;
6
- --rgb-background: 19 19 19;
7
- --rgb-surface: 19 19 19;
8
- --rgb-surface-lowest: 14 14 14;
9
- --rgb-surface-low: 28 27 27;
10
- --rgb-surface-container: 32 31 31;
11
- --rgb-surface-high: 42 42 42;
12
- --rgb-surface-highest: 53 53 52;
13
- --rgb-surface-bright: 58 57 57;
14
- --rgb-error: 255 180 171;
15
- --rgb-error-container: 147 0 10;
16
- --rgb-tertiary: 0 220 225;
17
- --rgb-success: 34 197 94;
18
- --color-background: #131313;
19
- --color-surface: #131313;
20
- --color-surface-lowest: #0e0e0e;
21
- --color-surface-container-lowest: var(--color-surface-lowest);
22
- --color-surface-low: #1c1b1b;
23
- --color-surface-container-low: var(--color-surface-low);
24
- --color-surface-container: #201f1f;
25
- --color-surface-high: #2a2a2a;
26
- --color-surface-container-high: var(--color-surface-high);
27
- --color-surface-highest: #353534;
28
- --color-surface-container-highest: var(--color-surface-highest);
29
- --color-surface-bright: #3a3939;
30
- --color-primary-container: #fce300;
31
- --color-primary-fixed: #fde403;
32
- --color-on-primary: #373100;
33
- --color-on-surface: #e5e2e1;
34
- --color-on-surface-variant: #cdc7ab;
35
- --color-outline-variant: #4b4732;
36
- --color-error: #ffb4ab;
37
- --color-error-container: #93000a;
38
- --color-tertiary-dim: #00dce1;
39
- --color-grid-dot: rgb(var(--rgb-primary) / 0.03);
40
- --color-success: #22c55e;
41
- --border-subtle: rgb(var(--rgb-outline) / 0.12);
42
- --border-medium: rgb(var(--rgb-outline) / 0.18);
43
- --border-strong: rgb(var(--rgb-outline) / 0.24);
44
- --text-muted: rgb(var(--rgb-on-surface) / 0.6);
45
- --text-subtle: rgb(var(--rgb-on-surface) / 0.45);
46
- --text-faint: rgb(var(--rgb-on-surface) / 0.35);
47
- --text-variant-muted: rgb(var(--rgb-on-surface-variant) / 0.55);
48
- --text-variant-subtle: rgb(var(--rgb-on-surface-variant) / 0.45);
49
- --primary-alpha-03: rgb(var(--rgb-primary) / 0.03);
50
- --primary-alpha-05: rgb(var(--rgb-primary) / 0.05);
51
- --primary-alpha-06: rgb(var(--rgb-primary) / 0.06);
52
- --primary-alpha-08: rgb(var(--rgb-primary) / 0.08);
53
- --primary-alpha-10: rgb(var(--rgb-primary) / 0.1);
54
- --primary-alpha-12: rgb(var(--rgb-primary) / 0.12);
55
- --primary-alpha-15: rgb(var(--rgb-primary) / 0.15);
56
- --primary-alpha-18: rgb(var(--rgb-primary) / 0.18);
57
- --primary-alpha-20: rgb(var(--rgb-primary) / 0.2);
58
- --primary-alpha-22: rgb(var(--rgb-primary) / 0.22);
59
- --primary-alpha-24: rgb(var(--rgb-primary) / 0.24);
60
- --primary-alpha-30: rgb(var(--rgb-primary) / 0.3);
61
- --primary-alpha-35: rgb(var(--rgb-primary) / 0.35);
62
- --focus-ring: 0 0 0 2px var(--color-surface), 0 0 0 4px var(--primary-alpha-30);
63
- --focus-ring-inset: inset 0 0 0 1px var(--primary-alpha-30), 0 0 0 2px var(--primary-alpha-12);
64
- --font-family-headline: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
65
- --font-family-body: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
66
- --font-family-mono: ui-monospace, "SFMono-Regular", "SF Mono", Consolas, "Liberation Mono", monospace;
67
- --font-size-status: 10px;
68
- --font-size-micro: 9px;
69
- --font-size-label: 11px;
70
- --font-size-icon: 18px;
71
- --control-height: 36px;
72
- --control-padding-inline: 12px;
73
- --button-height: 32px;
74
- --button-padding-inline: 10px;
75
- --button-corner-size: 10px;
76
- --spacing-1: 4px;
77
- --spacing-2: 8px;
78
- --spacing-3: 12px;
79
- --spacing-4: 16px;
80
- --spacing-5: 20px;
81
- --spacing-6: 24px;
82
- --spacing-8: 32px;
83
- --spacing-10: 40px;
84
- --radius-none: 0;
85
- --top-nav-height: 56px;
86
- --status-bar-height: 24px;
87
- --sidebar-width: 256px;
88
- --panel-width: 480px;
89
- --shadow-sidebar: 4px 0 0 0 #131313;
90
- --shadow-panel: -20px 0 60px rgb(0 0 0 / 0.8);
91
- --shadow-subtle-glow: 0 0 15px var(--primary-alpha-30);
92
- --shadow-strong-glow: 0 0 20px var(--primary-alpha-30);
93
- --transition-fast: 150ms ease;
94
- --transition-standard: 300ms ease;
95
- --duration-cursor-blink: 1s;
96
- --scrollbar-size: 4px;
2
+ --rgb-primary: 252 227 0;
3
+ --rgb-outline: 75 71 50;
4
+ --rgb-on-surface: 229 226 225;
5
+ --rgb-on-surface-variant: 205 199 171;
6
+ --rgb-background: 19 19 19;
7
+ --rgb-surface: 19 19 19;
8
+ --rgb-surface-lowest: 14 14 14;
9
+ --rgb-surface-low: 28 27 27;
10
+ --rgb-surface-container: 32 31 31;
11
+ --rgb-surface-high: 42 42 42;
12
+ --rgb-surface-highest: 53 53 52;
13
+ --rgb-surface-bright: 58 57 57;
14
+ --rgb-error: 255 180 171;
15
+ --rgb-error-container: 147 0 10;
16
+ --rgb-tertiary: 0 220 225;
17
+ --rgb-success: 34 197 94;
18
+ --color-background: #131313;
19
+ --color-surface: #131313;
20
+ --color-surface-lowest: #0e0e0e;
21
+ --color-surface-container-lowest: var(--color-surface-lowest);
22
+ --color-surface-low: #1c1b1b;
23
+ --color-surface-container-low: var(--color-surface-low);
24
+ --color-surface-container: #201f1f;
25
+ --color-surface-high: #2a2a2a;
26
+ --color-surface-container-high: var(--color-surface-high);
27
+ --color-surface-highest: #353534;
28
+ --color-surface-container-highest: var(--color-surface-highest);
29
+ --color-surface-bright: #3a3939;
30
+ --color-primary-container: #fce300;
31
+ --color-primary-fixed: #fde403;
32
+ --color-on-primary: #373100;
33
+ --color-on-surface: #e5e2e1;
34
+ --color-on-surface-variant: #cdc7ab;
35
+ --color-outline-variant: #4b4732;
36
+ --color-error: #ffb4ab;
37
+ --color-error-container: #93000a;
38
+ --color-tertiary-dim: #00dce1;
39
+ --color-grid-dot: rgb(var(--rgb-primary) / 0.03);
40
+ --color-success: #22c55e;
41
+ --border-subtle: rgb(var(--rgb-outline) / 0.12);
42
+ --border-medium: rgb(var(--rgb-outline) / 0.18);
43
+ --border-strong: rgb(var(--rgb-outline) / 0.24);
44
+ --text-muted: rgb(var(--rgb-on-surface) / 0.6);
45
+ --text-subtle: rgb(var(--rgb-on-surface) / 0.45);
46
+ --text-faint: rgb(var(--rgb-on-surface) / 0.35);
47
+ --text-variant-muted: rgb(var(--rgb-on-surface-variant) / 0.55);
48
+ --text-variant-subtle: rgb(var(--rgb-on-surface-variant) / 0.45);
49
+ --primary-alpha-03: rgb(var(--rgb-primary) / 0.03);
50
+ --primary-alpha-05: rgb(var(--rgb-primary) / 0.05);
51
+ --primary-alpha-06: rgb(var(--rgb-primary) / 0.06);
52
+ --primary-alpha-08: rgb(var(--rgb-primary) / 0.08);
53
+ --primary-alpha-10: rgb(var(--rgb-primary) / 0.1);
54
+ --primary-alpha-12: rgb(var(--rgb-primary) / 0.12);
55
+ --primary-alpha-15: rgb(var(--rgb-primary) / 0.15);
56
+ --primary-alpha-18: rgb(var(--rgb-primary) / 0.18);
57
+ --primary-alpha-20: rgb(var(--rgb-primary) / 0.2);
58
+ --primary-alpha-22: rgb(var(--rgb-primary) / 0.22);
59
+ --primary-alpha-24: rgb(var(--rgb-primary) / 0.24);
60
+ --primary-alpha-30: rgb(var(--rgb-primary) / 0.3);
61
+ --primary-alpha-35: rgb(var(--rgb-primary) / 0.35);
62
+ --focus-ring: 0 0 0 2px var(--color-surface), 0 0 0 4px var(--primary-alpha-30);
63
+ --focus-ring-inset: inset 0 0 0 1px var(--primary-alpha-30), 0 0 0 2px var(--primary-alpha-12);
64
+ --font-family-headline: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
65
+ --font-family-body: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
66
+ --font-family-mono: ui-monospace, 'SFMono-Regular', 'SF Mono', Consolas, 'Liberation Mono', monospace;
67
+ --font-size-status: 10px;
68
+ --font-size-micro: 9px;
69
+ --font-size-label: 11px;
70
+ --font-size-icon: 18px;
71
+ --control-height: 36px;
72
+ --control-padding-inline: 12px;
73
+ --button-height: 32px;
74
+ --button-padding-inline: 10px;
75
+ --button-corner-size: 10px;
76
+ --spacing-1: 4px;
77
+ --spacing-2: 8px;
78
+ --spacing-3: 12px;
79
+ --spacing-4: 16px;
80
+ --spacing-5: 20px;
81
+ --spacing-6: 24px;
82
+ --spacing-8: 32px;
83
+ --spacing-10: 40px;
84
+ --radius-none: 0;
85
+ --top-nav-height: 56px;
86
+ --status-bar-height: 24px;
87
+ --sidebar-width: 256px;
88
+ --panel-width: 600px;
89
+ --shadow-sidebar: 4px 0 0 0 #131313;
90
+ --shadow-panel: -20px 0 60px rgb(0 0 0 / 0.8);
91
+ --shadow-subtle-glow: 0 0 15px var(--primary-alpha-30);
92
+ --shadow-strong-glow: 0 0 20px var(--primary-alpha-30);
93
+ --transition-fast: 150ms ease;
94
+ --transition-standard: 300ms ease;
95
+ --duration-cursor-blink: 1s;
96
+ --scrollbar-size: 4px;
97
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlite-hub",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "SQLite-only local management app backend and SPA shell",
5
5
  "main": "server/server.js",
6
6
  "bin": {
@@ -28,6 +28,9 @@ function createDataRouter({ dataBrowserService }) {
28
28
  offset: req.query.offset,
29
29
  sortColumn: req.query.sortColumn,
30
30
  sortDirection: req.query.sortDirection,
31
+ filterColumn: req.query.filterColumn,
32
+ filterOperator: req.query.filterOperator,
33
+ filterValue: req.query.filterValue,
31
34
  });
32
35
 
33
36
  res.json(
@@ -1,34 +1,119 @@
1
1
  const express = require("express");
2
- const { route } = require("../utils/errors");
2
+ const { route, successResponse } = require("../utils/errors");
3
3
 
4
4
  function createExportRouter({ exportService }) {
5
5
  const router = express.Router();
6
6
 
7
- function sendCsv(res, result) {
8
- res.setHeader("Content-Type", "text/csv; charset=utf-8");
7
+ function sendExport(res, result) {
8
+ res.setHeader("Content-Type", result.mimeType || "text/plain; charset=utf-8");
9
9
  res.setHeader(
10
10
  "Content-Disposition",
11
11
  `attachment; filename="${result.filename}"`
12
12
  );
13
- res.send(result.csv);
13
+ res.send(result.content ?? result.csv ?? "");
14
14
  }
15
15
 
16
+ function sendQueryExport(res, sql, format) {
17
+ const result = exportService.exportQuery(sql, { format });
18
+ sendExport(res, result);
19
+ }
20
+
21
+ function exportTableFromBody(body, format) {
22
+ return exportService.exportTable(body?.tableName, {
23
+ sortColumn: body?.sortColumn,
24
+ sortDirection: body?.sortDirection,
25
+ filterColumn: body?.filterColumn,
26
+ filterOperator: body?.filterOperator,
27
+ filterValue: body?.filterValue,
28
+ format,
29
+ });
30
+ }
31
+
32
+ function sendTableExport(res, body, format) {
33
+ sendExport(res, exportTableFromBody(body, format));
34
+ }
35
+
36
+ router.post(
37
+ "/query",
38
+ route((req, res) => {
39
+ const result = exportService.exportQuery(req.body?.sql, {
40
+ format: req.body?.format || "csv",
41
+ });
42
+
43
+ res.json(
44
+ successResponse({
45
+ data: {
46
+ filename: result.filename,
47
+ content: result.content ?? result.csv ?? "",
48
+ format: result.format,
49
+ mimeType: result.mimeType,
50
+ columns: result.columns,
51
+ rowCount: result.rowCount,
52
+ },
53
+ })
54
+ );
55
+ })
56
+ );
57
+
16
58
  router.post(
17
59
  "/query.csv",
18
60
  route((req, res) => {
19
- const result = exportService.exportQuery(req.body.sql);
20
- sendCsv(res, result);
61
+ sendQueryExport(res, req.body?.sql, "csv");
62
+ })
63
+ );
64
+
65
+ router.post(
66
+ "/query.tsv",
67
+ route((req, res) => {
68
+ sendQueryExport(res, req.body?.sql, "tsv");
69
+ })
70
+ );
71
+
72
+ router.post(
73
+ "/query.md",
74
+ route((req, res) => {
75
+ sendQueryExport(res, req.body?.sql, "md");
76
+ })
77
+ );
78
+
79
+ router.post(
80
+ "/table",
81
+ route((req, res) => {
82
+ const result = exportTableFromBody(req.body, req.body?.format || "csv");
83
+
84
+ res.json(
85
+ successResponse({
86
+ data: {
87
+ filename: result.filename,
88
+ content: result.content ?? result.csv ?? "",
89
+ format: result.format,
90
+ mimeType: result.mimeType,
91
+ columns: result.columns,
92
+ rowCount: result.rowCount,
93
+ },
94
+ })
95
+ );
21
96
  })
22
97
  );
23
98
 
24
99
  router.post(
25
100
  "/table.csv",
26
101
  route((req, res) => {
27
- const result = exportService.exportTable(req.body?.tableName, {
28
- sortColumn: req.body?.sortColumn,
29
- sortDirection: req.body?.sortDirection,
30
- });
31
- sendCsv(res, result);
102
+ sendTableExport(res, req.body, "csv");
103
+ })
104
+ );
105
+
106
+ router.post(
107
+ "/table.tsv",
108
+ route((req, res) => {
109
+ sendTableExport(res, req.body, "tsv");
110
+ })
111
+ );
112
+
113
+ router.post(
114
+ "/table.md",
115
+ route((req, res) => {
116
+ sendTableExport(res, req.body, "md");
32
117
  })
33
118
  );
34
119
 
@@ -36,7 +121,7 @@ function createExportRouter({ exportService }) {
36
121
  "/table/:tableName.csv",
37
122
  route((req, res) => {
38
123
  const result = exportService.exportTable(req.params.tableName);
39
- sendCsv(res, result);
124
+ sendExport(res, result);
40
125
  })
41
126
  );
42
127
 
@@ -6,10 +6,11 @@ const {
6
6
  serializeRows,
7
7
  } = require("../../utils/sqliteTypes");
8
8
  const { getRawStructureEntries, getTableDetail } = require("./introspection");
9
+ const { normalizeTableFilter } = require("./tableFilter");
9
10
  const { buildTableOrderClause, normalizeTableSort } = require("./tableSort");
10
11
 
11
12
  const DEFAULT_LIMIT = 50;
12
- const MAX_LIMIT = 100;
13
+ const MAX_LIMIT = 250;
13
14
 
14
15
  function buildRowIdentity(tableDetail, row) {
15
16
  if (tableDetail.identityStrategy?.type === "rowid") {
@@ -100,15 +101,18 @@ class DataBrowserService {
100
101
  const tableDetail = getTableDetail(db, tableName);
101
102
  const { limit, offset } = normalizePaginationOptions(options);
102
103
  const sort = normalizeTableSort(tableDetail, options);
104
+ const filter = normalizeTableFilter(tableDetail, options);
103
105
  const selectExpression =
104
106
  tableDetail.identityStrategy?.type === "rowid" ? "rowid AS __rowid__, *" : "*";
105
107
  const orderClause = buildTableOrderClause(tableDetail, sort);
108
+ const whereClause = filter ? `WHERE ${filter.clause}` : "";
106
109
  const statement = db.prepare(
107
110
  [
108
111
  "SELECT",
109
112
  selectExpression,
110
113
  "FROM",
111
114
  quoteIdentifier(tableName),
115
+ whereClause,
112
116
  orderClause ? "ORDER BY" : "",
113
117
  orderClause,
114
118
  "LIMIT ? OFFSET ?",
@@ -116,11 +120,20 @@ class DataBrowserService {
116
120
  .filter(Boolean)
117
121
  .join(" ")
118
122
  );
119
- const rawRows = statement.all(limit, offset);
123
+ const rawRows = statement.all(...(filter?.params ?? []), limit, offset);
120
124
  const columns = statement
121
125
  .columns()
122
126
  .map((column) => column.name)
123
127
  .filter((columnName) => columnName !== "__rowid__");
128
+ const rowCount = filter
129
+ ? db
130
+ .prepare(
131
+ ["SELECT COUNT(*) AS count FROM", quoteIdentifier(tableName), whereClause]
132
+ .filter(Boolean)
133
+ .join(" ")
134
+ )
135
+ .get(...filter.params).count
136
+ : tableDetail.rowCount ?? rawRows.length;
124
137
 
125
138
  const rows = serializeRows(rawRows).map((row) => {
126
139
  const identity = buildRowIdentity(tableDetail, row);
@@ -138,17 +151,25 @@ class DataBrowserService {
138
151
  return {
139
152
  name: tableDetail.name,
140
153
  type: tableDetail.type,
141
- rowCount: tableDetail.rowCount ?? rows.length,
154
+ rowCount,
142
155
  limit,
143
156
  offset,
144
157
  page: Math.floor(offset / limit) + 1,
145
- pageCount: Math.max(1, Math.ceil((tableDetail.rowCount ?? rows.length) / limit)),
158
+ pageCount: Math.max(1, Math.ceil(rowCount / limit)),
146
159
  columns,
147
160
  columnMeta: tableDetail.columns,
148
161
  foreignKeys: tableDetail.foreignKeys,
149
162
  rows,
150
163
  identityStrategy: tableDetail.identityStrategy,
151
164
  notSafelyUpdatable: tableDetail.notSafelyUpdatable,
165
+ filter: filter
166
+ ? {
167
+ column: filter.column,
168
+ operator: filter.operator,
169
+ value: filter.value,
170
+ matchMode: filter.matchMode,
171
+ }
172
+ : null,
152
173
  sort,
153
174
  };
154
175
  }
@@ -1,7 +1,12 @@
1
1
  const { quoteIdentifier } = require("../../utils/identifier");
2
2
  const { serializeRows } = require("../../utils/sqliteTypes");
3
- const { rowsToCsv } = require("../../utils/csv");
3
+ const {
4
+ rowsToCsv,
5
+ rowsToDelimitedText,
6
+ rowsToMarkdownTable,
7
+ } = require("../../utils/csv");
4
8
  const { getTableDetail } = require("./introspection");
9
+ const { normalizeTableFilter } = require("./tableFilter");
5
10
  const { buildTableOrderClause, normalizeTableSort } = require("./tableSort");
6
11
  const {
7
12
  buildAutoTitle,
@@ -23,6 +28,43 @@ function sanitizeFilenameBase(value, fallback = "query-results") {
23
28
  return sanitized.slice(0, 120);
24
29
  }
25
30
 
31
+ const EXPORT_FORMATS = {
32
+ csv: {
33
+ extension: "csv",
34
+ mimeType: "text/csv; charset=utf-8",
35
+ },
36
+ tsv: {
37
+ extension: "tsv",
38
+ mimeType: "text/tab-separated-values; charset=utf-8",
39
+ },
40
+ md: {
41
+ extension: "md",
42
+ mimeType: "text/markdown; charset=utf-8",
43
+ },
44
+ };
45
+
46
+ function normalizeExportFormat(format) {
47
+ const normalized = String(format ?? "csv").toLowerCase();
48
+
49
+ if (!EXPORT_FORMATS[normalized]) {
50
+ throw new Error(`Unsupported export format: ${format}`);
51
+ }
52
+
53
+ return normalized;
54
+ }
55
+
56
+ function renderExportContent({ columns, rows, format, csvDelimiter }) {
57
+ if (format === "tsv") {
58
+ return rowsToDelimitedText({ columns, rows, delimiter: "\t" });
59
+ }
60
+
61
+ if (format === "md") {
62
+ return rowsToMarkdownTable({ columns, rows });
63
+ }
64
+
65
+ return rowsToCsv({ columns, rows, delimiter: csvDelimiter });
66
+ }
67
+
26
68
  class ExportService {
27
69
  constructor({ appStateStore, connectionManager, sqlExecutor }) {
28
70
  this.appStateStore = appStateStore;
@@ -34,7 +76,9 @@ class ExportService {
34
76
  return this.appStateStore.getSettings().csvDelimiter || ",";
35
77
  }
36
78
 
37
- exportQuery(sql) {
79
+ exportQuery(sql, options = {}) {
80
+ const format = normalizeExportFormat(options.format);
81
+ const formatConfig = EXPORT_FORMATS[format];
38
82
  const activeConnection = this.connectionManager.getActiveConnection();
39
83
  const historyItem = activeConnection
40
84
  ? this.appStateStore.findQueryHistoryItemBySql(activeConnection.id, sql)
@@ -50,44 +94,59 @@ class ExportService {
50
94
  persistHistory: false,
51
95
  requireReader: true,
52
96
  });
97
+ const content = renderExportContent({
98
+ columns: result.columns,
99
+ rows: result.rows,
100
+ format,
101
+ csvDelimiter: this.getDelimiter(),
102
+ });
53
103
 
54
104
  return {
55
- filename: `${filenameBase}.csv`,
56
- csv: rowsToCsv({
57
- columns: result.columns,
58
- rows: result.rows,
59
- delimiter: this.getDelimiter(),
60
- }),
105
+ filename: `${filenameBase}.${formatConfig.extension}`,
106
+ content,
107
+ csv: format === "csv" ? content : undefined,
108
+ format,
109
+ mimeType: formatConfig.mimeType,
61
110
  columns: result.columns,
62
111
  rowCount: result.rows.length,
63
112
  };
64
113
  }
65
114
 
66
115
  exportTable(tableName, options = {}) {
116
+ const format = normalizeExportFormat(options.format);
117
+ const formatConfig = EXPORT_FORMATS[format];
67
118
  const db = this.connectionManager.getActiveDatabase();
68
119
  const tableDetail = getTableDetail(db, tableName, { includeRowCount: false });
69
120
  const sort = normalizeTableSort(tableDetail, options);
121
+ const filter = normalizeTableFilter(tableDetail, options);
70
122
  const orderClause = buildTableOrderClause(tableDetail, sort);
123
+ const whereClause = filter ? `WHERE ${filter.clause}` : "";
71
124
  const statement = db.prepare(
72
125
  [
73
126
  "SELECT * FROM",
74
127
  quoteIdentifier(tableName),
128
+ whereClause,
75
129
  orderClause ? "ORDER BY" : "",
76
130
  orderClause,
77
131
  ]
78
132
  .filter(Boolean)
79
133
  .join(" ")
80
134
  );
81
- const rows = serializeRows(statement.all());
135
+ const rows = serializeRows(statement.all(...(filter?.params ?? [])));
82
136
  const columns = statement.columns().map((column) => column.name);
137
+ const content = renderExportContent({
138
+ columns,
139
+ rows,
140
+ format,
141
+ csvDelimiter: this.getDelimiter(),
142
+ });
83
143
 
84
144
  return {
85
- filename: `${tableName}.csv`,
86
- csv: rowsToCsv({
87
- columns,
88
- rows,
89
- delimiter: this.getDelimiter(),
90
- }),
145
+ filename: `${tableName}.${formatConfig.extension}`,
146
+ content,
147
+ csv: format === "csv" ? content : undefined,
148
+ format,
149
+ mimeType: formatConfig.mimeType,
91
150
  columns,
92
151
  rowCount: rows.length,
93
152
  };