sqlite-hub 0.1.3
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/.npmingnore +4 -0
- package/README.md +46 -0
- package/assets/images/logo.webp +0 -0
- package/assets/images/logo_extrasmall.webp +0 -0
- package/assets/images/logo_raw.png +0 -0
- package/assets/images/logo_small.webp +0 -0
- package/assets/mockups/connections.png +0 -0
- package/assets/mockups/data.png +0 -0
- package/assets/mockups/data_edit.png +0 -0
- package/assets/mockups/home.png +0 -0
- package/assets/mockups/overview.png +0 -0
- package/assets/mockups/sql_editor.png +0 -0
- package/assets/mockups/structure.png +0 -0
- package/bin/sqlite-hub.js +116 -0
- package/changelog.md +3 -0
- package/data/.gitkeep +0 -0
- package/index.html +100 -0
- package/js/api.js +193 -0
- package/js/app.js +520 -0
- package/js/components/actionBar.js +8 -0
- package/js/components/appShell.js +17 -0
- package/js/components/badges.js +5 -0
- package/js/components/bottomTabs.js +37 -0
- package/js/components/connectionCard.js +106 -0
- package/js/components/dataGrid.js +47 -0
- package/js/components/emptyState.js +159 -0
- package/js/components/metricCard.js +32 -0
- package/js/components/modal.js +317 -0
- package/js/components/pageHeader.js +33 -0
- package/js/components/queryEditor.js +121 -0
- package/js/components/queryResults.js +107 -0
- package/js/components/rowEditorPanel.js +164 -0
- package/js/components/sidebar.js +57 -0
- package/js/components/statusBar.js +39 -0
- package/js/components/toast.js +39 -0
- package/js/components/topNav.js +27 -0
- package/js/router.js +66 -0
- package/js/store.js +1092 -0
- package/js/utils/format.js +179 -0
- package/js/views/connections.js +133 -0
- package/js/views/data.js +400 -0
- package/js/views/editor.js +259 -0
- package/js/views/landing.js +11 -0
- package/js/views/overview.js +220 -0
- package/js/views/settings.js +109 -0
- package/js/views/structure.js +242 -0
- package/package.json +18 -0
- package/publish_brew.sh +444 -0
- package/publish_npm.sh +241 -0
- package/server/routes/connections.js +146 -0
- package/server/routes/data.js +59 -0
- package/server/routes/export.js +25 -0
- package/server/routes/overview.js +39 -0
- package/server/routes/settings.js +50 -0
- package/server/routes/sql.js +50 -0
- package/server/routes/structure.js +38 -0
- package/server/server.js +136 -0
- package/server/services/sqlite/connectionManager.js +306 -0
- package/server/services/sqlite/dataBrowserService.js +255 -0
- package/server/services/sqlite/exportService.js +34 -0
- package/server/services/sqlite/importService.js +111 -0
- package/server/services/sqlite/introspection.js +302 -0
- package/server/services/sqlite/overviewService.js +109 -0
- package/server/services/sqlite/sqlExecutor.js +434 -0
- package/server/services/sqlite/structureService.js +60 -0
- package/server/services/storage/appStateStore.js +530 -0
- package/server/utils/csv.js +34 -0
- package/server/utils/errors.js +175 -0
- package/server/utils/fileValidation.js +135 -0
- package/server/utils/identifier.js +38 -0
- package/server/utils/sqliteTypes.js +112 -0
- package/styles/base.css +176 -0
- package/styles/components.css +323 -0
- package/styles/layout.css +101 -0
- package/styles/tokens.css +49 -0
- package/styles/views.css +84 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
class AppError extends Error {
|
|
2
|
+
constructor(message, statusCode = 500, options = {}) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = this.constructor.name;
|
|
5
|
+
this.statusCode = statusCode;
|
|
6
|
+
this.code = options.code ?? "APP_ERROR";
|
|
7
|
+
this.details = options.details ?? null;
|
|
8
|
+
this.sqliteCode = options.sqliteCode ?? null;
|
|
9
|
+
this.warnings = options.warnings ?? [];
|
|
10
|
+
this.expose = options.expose ?? true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class ValidationError extends AppError {
|
|
15
|
+
constructor(message, options = {}) {
|
|
16
|
+
super(message, 400, { code: "VALIDATION_ERROR", ...options });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class NotFoundError extends AppError {
|
|
21
|
+
constructor(message, options = {}) {
|
|
22
|
+
super(message, 404, { code: "NOT_FOUND", ...options });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class ConflictError extends AppError {
|
|
27
|
+
constructor(message, options = {}) {
|
|
28
|
+
super(message, 409, { code: "CONFLICT", ...options });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class BusyError extends AppError {
|
|
33
|
+
constructor(message, options = {}) {
|
|
34
|
+
super(message, 423, { code: "SQLITE_BUSY", ...options });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class ReadOnlyError extends AppError {
|
|
39
|
+
constructor(message, options = {}) {
|
|
40
|
+
super(message, 403, { code: "SQLITE_READONLY", ...options });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class DatabaseRequiredError extends AppError {
|
|
45
|
+
constructor(message = "No active SQLite database selected.", options = {}) {
|
|
46
|
+
super(message, 400, { code: "ACTIVE_DATABASE_REQUIRED", ...options });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mapSqliteError(error) {
|
|
51
|
+
if (!error) {
|
|
52
|
+
return new AppError("Unknown error");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (error instanceof AppError) {
|
|
56
|
+
return error;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const code = error.code ?? "";
|
|
60
|
+
const message = error.message ?? "Unexpected error";
|
|
61
|
+
|
|
62
|
+
if (code.includes("SQLITE_BUSY") || code.includes("SQLITE_LOCKED")) {
|
|
63
|
+
return new BusyError(message, {
|
|
64
|
+
details: error,
|
|
65
|
+
sqliteCode: code,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (code.includes("SQLITE_READONLY")) {
|
|
70
|
+
return new ReadOnlyError(message, {
|
|
71
|
+
details: error,
|
|
72
|
+
sqliteCode: code,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
code.includes("SQLITE_ERROR") ||
|
|
78
|
+
code.includes("SQLITE_CONSTRAINT") ||
|
|
79
|
+
code.includes("SQLITE_MISMATCH") ||
|
|
80
|
+
code.includes("SQLITE_RANGE")
|
|
81
|
+
) {
|
|
82
|
+
return new ValidationError(message, {
|
|
83
|
+
code: code || "SQLITE_ERROR",
|
|
84
|
+
details: error,
|
|
85
|
+
sqliteCode: code || null,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (message.includes("file is not a database")) {
|
|
90
|
+
return new ValidationError(message, {
|
|
91
|
+
code: "SQLITE_INVALID_DATABASE",
|
|
92
|
+
details: error,
|
|
93
|
+
sqliteCode: code || null,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new AppError(message, 500, {
|
|
98
|
+
code: code || "INTERNAL_ERROR",
|
|
99
|
+
details: error,
|
|
100
|
+
sqliteCode: code || null,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function successResponse({
|
|
105
|
+
data = null,
|
|
106
|
+
metadata = {},
|
|
107
|
+
message = "",
|
|
108
|
+
warnings = [],
|
|
109
|
+
timingMs = null,
|
|
110
|
+
readOnly = undefined,
|
|
111
|
+
} = {}) {
|
|
112
|
+
const response = {
|
|
113
|
+
success: true,
|
|
114
|
+
message,
|
|
115
|
+
data,
|
|
116
|
+
metadata,
|
|
117
|
+
warnings,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (typeof timingMs === "number") {
|
|
121
|
+
response.timingMs = timingMs;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof readOnly === "boolean") {
|
|
125
|
+
response.readOnly = readOnly;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return response;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function errorResponse(error) {
|
|
132
|
+
const normalized = mapSqliteError(error);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
message: normalized.message,
|
|
137
|
+
error: {
|
|
138
|
+
code: normalized.code,
|
|
139
|
+
message: normalized.message,
|
|
140
|
+
details: normalized.details,
|
|
141
|
+
sqliteCode: normalized.sqliteCode,
|
|
142
|
+
},
|
|
143
|
+
warnings: normalized.warnings ?? [],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function route(handler) {
|
|
148
|
+
return async (req, res, next) => {
|
|
149
|
+
try {
|
|
150
|
+
await handler(req, res, next);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
next(error);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function errorMiddleware(error, req, res, next) {
|
|
158
|
+
const normalized = mapSqliteError(error);
|
|
159
|
+
res.status(normalized.statusCode).json(errorResponse(normalized));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
AppError,
|
|
164
|
+
BusyError,
|
|
165
|
+
ConflictError,
|
|
166
|
+
DatabaseRequiredError,
|
|
167
|
+
NotFoundError,
|
|
168
|
+
ReadOnlyError,
|
|
169
|
+
ValidationError,
|
|
170
|
+
errorMiddleware,
|
|
171
|
+
errorResponse,
|
|
172
|
+
mapSqliteError,
|
|
173
|
+
route,
|
|
174
|
+
successResponse,
|
|
175
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
const { ConflictError, NotFoundError, ValidationError } = require("./errors");
|
|
5
|
+
|
|
6
|
+
const SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3"]);
|
|
7
|
+
const SQL_DUMP_EXTENSIONS = new Set([".sql"]);
|
|
8
|
+
const SQLITE_HEADER = Buffer.from("SQLite format 3\0", "utf8");
|
|
9
|
+
|
|
10
|
+
function expandHome(filePath) {
|
|
11
|
+
if (typeof filePath !== "string" || !filePath.trim()) {
|
|
12
|
+
throw new ValidationError("A file path is required.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (filePath === "~") {
|
|
16
|
+
return os.homedir();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (filePath.startsWith("~/")) {
|
|
20
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return filePath;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveUserPath(filePath) {
|
|
27
|
+
return path.resolve(expandHome(filePath));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function assertExtension(filePath, allowedExtensions, label) {
|
|
31
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
32
|
+
|
|
33
|
+
if (!allowedExtensions.has(extension)) {
|
|
34
|
+
throw new ValidationError(
|
|
35
|
+
`${label} must use one of: ${Array.from(allowedExtensions).join(", ")}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ensureFileExists(filePath, label = "File") {
|
|
41
|
+
if (!fs.existsSync(filePath)) {
|
|
42
|
+
throw new NotFoundError(`${label} does not exist: ${filePath}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ensureFileDoesNotExist(filePath, label = "File") {
|
|
47
|
+
if (fs.existsSync(filePath)) {
|
|
48
|
+
throw new ConflictError(`${label} already exists: ${filePath}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ensureParentDirectory(filePath) {
|
|
53
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readSqliteHeader(filePath) {
|
|
57
|
+
const handle = fs.openSync(filePath, "r");
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const header = Buffer.alloc(SQLITE_HEADER.length);
|
|
61
|
+
const bytesRead = fs.readSync(handle, header, 0, SQLITE_HEADER.length, 0);
|
|
62
|
+
return header.subarray(0, bytesRead);
|
|
63
|
+
} finally {
|
|
64
|
+
fs.closeSync(handle);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isRealSqliteDatabase(filePath) {
|
|
69
|
+
ensureFileExists(filePath, "SQLite database");
|
|
70
|
+
|
|
71
|
+
const stat = fs.statSync(filePath);
|
|
72
|
+
|
|
73
|
+
if (!stat.isFile()) {
|
|
74
|
+
throw new ValidationError(`SQLite database path is not a file: ${filePath}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (stat.size < SQLITE_HEADER.length) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return readSqliteHeader(filePath).equals(SQLITE_HEADER);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function validateSqlitePath(inputPath, { mustExist = true } = {}) {
|
|
85
|
+
const filePath = resolveUserPath(inputPath);
|
|
86
|
+
assertExtension(filePath, SQLITE_EXTENSIONS, "SQLite database");
|
|
87
|
+
|
|
88
|
+
if (mustExist) {
|
|
89
|
+
ensureFileExists(filePath, "SQLite database");
|
|
90
|
+
|
|
91
|
+
if (!isRealSqliteDatabase(filePath)) {
|
|
92
|
+
throw new ValidationError(`File is not a valid SQLite database: ${filePath}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return filePath;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function validateSqlDumpPath(inputPath) {
|
|
100
|
+
const filePath = resolveUserPath(inputPath);
|
|
101
|
+
assertExtension(filePath, SQL_DUMP_EXTENSIONS, "SQL dump");
|
|
102
|
+
ensureFileExists(filePath, "SQL dump");
|
|
103
|
+
return filePath;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isWritable(filePath) {
|
|
107
|
+
try {
|
|
108
|
+
fs.accessSync(filePath, fs.constants.W_OK);
|
|
109
|
+
return true;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getFileMetadata(filePath) {
|
|
116
|
+
const stat = fs.statSync(filePath);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
path: filePath,
|
|
120
|
+
sizeBytes: stat.size,
|
|
121
|
+
lastModifiedAt: stat.mtime.toISOString(),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
SQLITE_EXTENSIONS,
|
|
127
|
+
ensureFileDoesNotExist,
|
|
128
|
+
ensureParentDirectory,
|
|
129
|
+
getFileMetadata,
|
|
130
|
+
isRealSqliteDatabase,
|
|
131
|
+
isWritable,
|
|
132
|
+
resolveUserPath,
|
|
133
|
+
validateSqlDumpPath,
|
|
134
|
+
validateSqlitePath,
|
|
135
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { ValidationError } = require("./errors");
|
|
2
|
+
|
|
3
|
+
function assertValidIdentifier(identifier, label = "Identifier") {
|
|
4
|
+
if (typeof identifier !== "string" || !identifier.trim()) {
|
|
5
|
+
throw new ValidationError(`${label} is required.`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (identifier.includes("\0")) {
|
|
9
|
+
throw new ValidationError(`${label} contains invalid characters.`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return identifier;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function quoteIdentifier(identifier) {
|
|
16
|
+
return `"${assertValidIdentifier(identifier).replaceAll('"', '""')}"`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ensureKnownIdentifier(identifier, allowed, label = "Identifier") {
|
|
20
|
+
assertValidIdentifier(identifier, label);
|
|
21
|
+
|
|
22
|
+
if (!allowed.includes(identifier)) {
|
|
23
|
+
throw new ValidationError(`${label} is not part of the current SQLite schema.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return identifier;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function quoteIdentifierList(identifiers) {
|
|
30
|
+
return identifiers.map((identifier) => quoteIdentifier(identifier)).join(", ");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
assertValidIdentifier,
|
|
35
|
+
ensureKnownIdentifier,
|
|
36
|
+
quoteIdentifier,
|
|
37
|
+
quoteIdentifierList,
|
|
38
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const { Buffer } = require("node:buffer");
|
|
2
|
+
|
|
3
|
+
function normalizeDeclaredType(type) {
|
|
4
|
+
const declaredType = String(type ?? "").trim().toUpperCase();
|
|
5
|
+
|
|
6
|
+
if (!declaredType) {
|
|
7
|
+
return {
|
|
8
|
+
declaredType: "",
|
|
9
|
+
affinity: "BLOB",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (declaredType.includes("INT")) {
|
|
14
|
+
return { declaredType, affinity: "INTEGER" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (
|
|
18
|
+
declaredType.includes("CHAR") ||
|
|
19
|
+
declaredType.includes("CLOB") ||
|
|
20
|
+
declaredType.includes("TEXT")
|
|
21
|
+
) {
|
|
22
|
+
return { declaredType, affinity: "TEXT" };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (declaredType.includes("BLOB")) {
|
|
26
|
+
return { declaredType, affinity: "BLOB" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
declaredType.includes("REAL") ||
|
|
31
|
+
declaredType.includes("FLOA") ||
|
|
32
|
+
declaredType.includes("DOUB")
|
|
33
|
+
) {
|
|
34
|
+
return { declaredType, affinity: "REAL" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { declaredType, affinity: "NUMERIC" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function serializeBlob(buffer) {
|
|
41
|
+
return {
|
|
42
|
+
__type: "blob",
|
|
43
|
+
sizeBytes: buffer.length,
|
|
44
|
+
hexPreview: buffer.subarray(0, 16).toString("hex"),
|
|
45
|
+
base64Preview: buffer.subarray(0, 24).toString("base64"),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function serializeSqliteValue(value) {
|
|
50
|
+
if (Buffer.isBuffer(value)) {
|
|
51
|
+
return serializeBlob(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (value instanceof Uint8Array) {
|
|
55
|
+
return serializeBlob(Buffer.from(value));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function serializeRow(row) {
|
|
62
|
+
return Object.fromEntries(
|
|
63
|
+
Object.entries(row).map(([key, value]) => [key, serializeSqliteValue(value)])
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function serializeRows(rows) {
|
|
68
|
+
return rows.map((row) => serializeRow(row));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function decodeBlobPayload(payload) {
|
|
72
|
+
const encoding = payload.encoding ?? "base64";
|
|
73
|
+
const rawData = payload.data ?? payload.value ?? "";
|
|
74
|
+
|
|
75
|
+
if (typeof rawData !== "string") {
|
|
76
|
+
throw new Error("BLOB payload data must be a string.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (encoding === "hex") {
|
|
80
|
+
return Buffer.from(rawData, "hex");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (encoding === "base64") {
|
|
84
|
+
return Buffer.from(rawData, "base64");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error(`Unsupported BLOB encoding: ${encoding}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function deserializeSqliteValue(value) {
|
|
91
|
+
if (value === undefined) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (value && typeof value === "object" && value.__type === "blob") {
|
|
96
|
+
return decodeBlobPayload(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(value) || (value && typeof value === "object")) {
|
|
100
|
+
return JSON.stringify(value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
deserializeSqliteValue,
|
|
108
|
+
normalizeDeclaredType,
|
|
109
|
+
serializeRow,
|
|
110
|
+
serializeRows,
|
|
111
|
+
serializeSqliteValue,
|
|
112
|
+
};
|
package/styles/base.css
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
*,
|
|
2
|
+
*::before,
|
|
3
|
+
*::after {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
html,
|
|
8
|
+
body {
|
|
9
|
+
height: 100%;
|
|
10
|
+
margin: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
background: var(--color-background);
|
|
15
|
+
color: var(--color-on-surface);
|
|
16
|
+
font-family: var(--font-family-body);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
button,
|
|
20
|
+
input,
|
|
21
|
+
select,
|
|
22
|
+
textarea {
|
|
23
|
+
color: inherit;
|
|
24
|
+
font: inherit;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
button {
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
a {
|
|
32
|
+
color: inherit;
|
|
33
|
+
text-decoration: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
img {
|
|
37
|
+
display: block;
|
|
38
|
+
max-width: 100%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[hidden] {
|
|
42
|
+
display: none !important;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.font-headline {
|
|
46
|
+
font-family: var(--font-family-headline);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.font-body {
|
|
50
|
+
font-family: var(--font-family-body);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.font-mono {
|
|
54
|
+
font-family: var(--font-family-mono);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.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;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.no-scrollbar {
|
|
71
|
+
-ms-overflow-style: none;
|
|
72
|
+
scrollbar-width: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.no-scrollbar::-webkit-scrollbar {
|
|
76
|
+
display: none;
|
|
77
|
+
height: 0;
|
|
78
|
+
width: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.custom-scrollbar {
|
|
82
|
+
scrollbar-color: var(--color-surface-highest) var(--color-surface-lowest);
|
|
83
|
+
scrollbar-width: thin;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.custom-scrollbar::-webkit-scrollbar {
|
|
87
|
+
height: var(--scrollbar-size);
|
|
88
|
+
width: var(--scrollbar-size);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.custom-scrollbar::-webkit-scrollbar-track {
|
|
92
|
+
background: var(--color-surface-lowest);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
96
|
+
background: var(--color-surface-highest);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
100
|
+
background: var(--color-primary-container);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.clipped-btn,
|
|
104
|
+
.clipped-corner,
|
|
105
|
+
.custom-clip,
|
|
106
|
+
.clip-button,
|
|
107
|
+
.clip-corner {
|
|
108
|
+
clip-path: var(--clip-path);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.sql-keyword {
|
|
112
|
+
color: var(--color-primary-container);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.sql-string {
|
|
116
|
+
color: var(--color-on-surface);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.sql-comment {
|
|
120
|
+
color: var(--color-outline-variant);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.sql-value {
|
|
124
|
+
color: var(--color-on-surface-variant);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.query-editor-layer {
|
|
128
|
+
min-height: 140px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.query-editor-highlight,
|
|
132
|
+
.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;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.query-editor-highlight {
|
|
143
|
+
color: var(--color-on-surface);
|
|
144
|
+
inset: 0;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
pointer-events: none;
|
|
147
|
+
position: absolute;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.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;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.query-editor-input::placeholder {
|
|
159
|
+
color: transparent;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.cursor-blink {
|
|
163
|
+
animation: cursor-blink var(--duration-cursor-blink) step-end infinite;
|
|
164
|
+
border-right: 2px solid var(--color-primary-container);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@keyframes cursor-blink {
|
|
168
|
+
from,
|
|
169
|
+
to {
|
|
170
|
+
border-color: transparent;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
50% {
|
|
174
|
+
border-color: var(--color-primary-container);
|
|
175
|
+
}
|
|
176
|
+
}
|