mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-28 08:08:02 +00:00
993 lines
31 KiB
TypeScript
993 lines
31 KiB
TypeScript
import type {
|
|
ConnectionConfig,
|
|
DatabaseInfo,
|
|
TableInfo,
|
|
ObjectInfo,
|
|
ObjectSource,
|
|
ObjectSourceKind,
|
|
ColumnInfo,
|
|
IndexInfo,
|
|
ForeignKeyInfo,
|
|
TriggerInfo,
|
|
QueryResult,
|
|
SqlReferenceAnalysis,
|
|
InstalledPlugin,
|
|
JdbcDriverInfo,
|
|
JdbcPluginStatus,
|
|
SidebarLayout,
|
|
SavedSqlFile,
|
|
SavedSqlFolder,
|
|
SavedSqlLibrary,
|
|
} from "@/types/database";
|
|
import type { AiConfig } from "@/stores/settingsStore";
|
|
import type {
|
|
AgentDriverInfo,
|
|
AiCompletionRequest,
|
|
AiStreamChunk,
|
|
AiConversation,
|
|
AiModelInfo,
|
|
DesktopSettings,
|
|
DriverInstallProgress,
|
|
JavaRuntimeConfig,
|
|
UpdateInfo,
|
|
RedisDatabaseInfo,
|
|
RedisValue,
|
|
RedisScanResult,
|
|
RedisCommandResult,
|
|
MongoDocumentResult,
|
|
HistoryEntry,
|
|
SqlFileRequest,
|
|
SqlFilePreview,
|
|
SqlFileProgress,
|
|
TransferRequest,
|
|
TransferProgress,
|
|
TableImportPreview,
|
|
TableImportRequest,
|
|
TableImportSummary,
|
|
TableImportProgress,
|
|
DatabaseExportRequest,
|
|
ExportProgress,
|
|
XlsxCellValue,
|
|
} from "./tauri";
|
|
import type { QueryEditability } from "@/lib/sqlAnalysis";
|
|
import type { DataGridSaveStatementOptions } from "@/lib/dataGridSql";
|
|
import type { DataGridSavePreparation } from "./tauri";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
async function post<T>(url: string, body: unknown): Promise<T> {
|
|
const res = await fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
if (!res.ok) throw new Error(await res.text());
|
|
return res.json();
|
|
}
|
|
|
|
async function get<T>(url: string): Promise<T> {
|
|
const res = await fetch(url);
|
|
if (!res.ok) throw new Error(await res.text());
|
|
return res.json();
|
|
}
|
|
|
|
async function del<T>(url: string): Promise<T> {
|
|
const res = await fetch(url, { method: "DELETE" });
|
|
if (!res.ok) throw new Error(await res.text());
|
|
return res.json();
|
|
}
|
|
|
|
function qs(params: Record<string, string | number | undefined>): string {
|
|
const sp = new URLSearchParams();
|
|
for (const [k, v] of Object.entries(params)) {
|
|
if (v !== undefined && v !== null) sp.set(k, String(v));
|
|
}
|
|
return sp.toString();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Connection
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function testConnection(config: ConnectionConfig): Promise<string> {
|
|
return post("/api/connection/test", { config });
|
|
}
|
|
|
|
export async function connectDb(config: ConnectionConfig): Promise<string> {
|
|
return post("/api/connection/connect", { config });
|
|
}
|
|
|
|
export async function disconnectDb(connectionId: string): Promise<void> {
|
|
return post("/api/connection/disconnect", { connectionId });
|
|
}
|
|
|
|
export async function saveConnections(configs: ConnectionConfig[]): Promise<void> {
|
|
return post("/api/connection/save", { configs });
|
|
}
|
|
|
|
export async function loadConnections(): Promise<ConnectionConfig[]> {
|
|
return get("/api/connection/list");
|
|
}
|
|
|
|
export async function listSystemFonts(): Promise<string[]> {
|
|
return [];
|
|
}
|
|
|
|
export async function listPlugins(): Promise<InstalledPlugin[]> {
|
|
return get("/api/plugins");
|
|
}
|
|
|
|
export async function listJdbcDrivers(): Promise<JdbcDriverInfo[]> {
|
|
return [];
|
|
}
|
|
|
|
export async function importJdbcDrivers(_paths: string[]): Promise<JdbcDriverInfo[]> {
|
|
return [];
|
|
}
|
|
|
|
export async function deleteJdbcDriver(_path: string): Promise<JdbcDriverInfo[]> {
|
|
return [];
|
|
}
|
|
|
|
export async function jdbcPluginStatus(): Promise<JdbcPluginStatus> {
|
|
return {
|
|
installed: false,
|
|
version: null,
|
|
protocol_version: null,
|
|
compatible: true,
|
|
latest_version: null,
|
|
latest_protocol_version: null,
|
|
update_available: false,
|
|
path: "",
|
|
};
|
|
}
|
|
|
|
export async function installJdbcPlugin(): Promise<JdbcPluginStatus> {
|
|
return jdbcPluginStatus();
|
|
}
|
|
|
|
export async function installJdbcPluginLocal(_path: string): Promise<JdbcPluginStatus> {
|
|
return jdbcPluginStatus();
|
|
}
|
|
|
|
export async function uninstallJdbcPlugin(): Promise<JdbcPluginStatus> {
|
|
return jdbcPluginStatus();
|
|
}
|
|
|
|
export async function listInstalledAgentsLocal(): Promise<AgentDriverInfo[]> {
|
|
return get("/api/agents/installed-local");
|
|
}
|
|
|
|
export async function listInstalledAgents(): Promise<AgentDriverInfo[]> {
|
|
return get("/api/agents/installed");
|
|
}
|
|
|
|
export async function installAgent(dbType: string): Promise<void> {
|
|
await post("/api/agents/install", { dbType });
|
|
}
|
|
|
|
export async function upgradeAllAgents(): Promise<number> {
|
|
const result: { count: number } = await post("/api/agents/upgrade-all", {});
|
|
return result.count;
|
|
}
|
|
|
|
export async function uninstallAgent(dbType: string): Promise<void> {
|
|
await post("/api/agents/uninstall", { dbType });
|
|
}
|
|
|
|
export async function getAgentJavaRuntimeConfig(): Promise<JavaRuntimeConfig> {
|
|
return get("/api/agents/java-runtime");
|
|
}
|
|
|
|
export async function setAgentJavaRuntimeConfig(config: JavaRuntimeConfig): Promise<JavaRuntimeConfig> {
|
|
return post("/api/agents/java-runtime", { config });
|
|
}
|
|
|
|
export async function invalidateAgentRegistryCache(): Promise<void> {
|
|
await post("/api/agents/invalidate-registry-cache", {});
|
|
}
|
|
|
|
export async function importAgentsFromZip(fileOrPath: string | File): Promise<number> {
|
|
if (typeof fileOrPath === "string") {
|
|
throw new Error("Offline ZIP import in web mode requires a File object, not a file path");
|
|
}
|
|
const formData = new FormData();
|
|
formData.append("file", fileOrPath);
|
|
const res = await fetch("/api/agents/import-offline", { method: "POST", body: formData });
|
|
if (!res.ok) throw new Error(await res.text());
|
|
const result: { count: number } = await res.json();
|
|
return result.count;
|
|
}
|
|
|
|
export async function importAgentJar(_dbType: string, _path: string): Promise<void> {
|
|
throw new Error("Local JAR import is only available in the desktop app");
|
|
}
|
|
|
|
export async function reinstallJre(jreKey?: string): Promise<void> {
|
|
await post("/api/agents/reinstall-jre", { jreKey });
|
|
}
|
|
|
|
export async function uninstallJre(jreKey: string): Promise<void> {
|
|
await post("/api/agents/uninstall-jre", { jreKey });
|
|
}
|
|
|
|
export async function listenAgentInstallProgress(
|
|
handler: (progress: DriverInstallProgress) => void,
|
|
): Promise<() => void> {
|
|
const es = new EventSource("/api/agents/progress/global");
|
|
es.onmessage = (event) => {
|
|
try {
|
|
handler(JSON.parse(event.data));
|
|
} catch {
|
|
/* ignore malformed progress events */
|
|
}
|
|
};
|
|
return () => es.close();
|
|
}
|
|
|
|
export async function loadSavedSqlLibrary(): Promise<SavedSqlLibrary> {
|
|
return get("/api/saved-sql");
|
|
}
|
|
|
|
export async function saveSavedSqlFolder(folder: SavedSqlFolder): Promise<SavedSqlFolder> {
|
|
return post("/api/saved-sql/folders", folder);
|
|
}
|
|
|
|
export async function deleteSavedSqlFolder(id: string): Promise<void> {
|
|
return del(`/api/saved-sql/folders/${encodeURIComponent(id)}`);
|
|
}
|
|
|
|
export async function saveSavedSqlFile(file: SavedSqlFile): Promise<SavedSqlFile> {
|
|
return post("/api/saved-sql", file);
|
|
}
|
|
|
|
export async function deleteSavedSqlFile(id: string): Promise<void> {
|
|
return del(`/api/saved-sql/${encodeURIComponent(id)}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Schema
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function listDatabases(connectionId: string): Promise<DatabaseInfo[]> {
|
|
return get(`/api/schema/databases?${qs({ connection_id: connectionId })}`);
|
|
}
|
|
|
|
export async function saveSchemaCache(cacheKey: string, payload: unknown): Promise<void> {
|
|
return post("/api/schema/cache", { cacheKey, payload });
|
|
}
|
|
|
|
export async function loadSchemaCache<T = unknown>(cacheKey: string): Promise<T | null> {
|
|
return get(`/api/schema/cache?${qs({ cache_key: cacheKey })}`);
|
|
}
|
|
|
|
export async function deleteSchemaCachePrefix(prefix: string): Promise<void> {
|
|
return del(`/api/schema/cache-prefix?${qs({ prefix })}`);
|
|
}
|
|
|
|
export async function listSchemas(connectionId: string, database: string): Promise<string[]> {
|
|
return get(`/api/schema/schemas?${qs({ connection_id: connectionId, database })}`);
|
|
}
|
|
|
|
export async function listTables(
|
|
connectionId: string,
|
|
database: string,
|
|
schema: string,
|
|
filter?: string,
|
|
limit?: number,
|
|
): Promise<TableInfo[]> {
|
|
return get(`/api/schema/tables?${qs({ connection_id: connectionId, database, schema, filter, limit })}`);
|
|
}
|
|
|
|
export async function listObjects(connectionId: string, database: string, schema: string): Promise<ObjectInfo[]> {
|
|
return get(`/api/schema/objects?${qs({ connection_id: connectionId, database, schema })}`);
|
|
}
|
|
|
|
export async function getObjectSource(
|
|
connectionId: string,
|
|
database: string,
|
|
schema: string,
|
|
name: string,
|
|
objectType: ObjectSourceKind,
|
|
): Promise<ObjectSource> {
|
|
return get(
|
|
`/api/schema/object-source?${qs({ connection_id: connectionId, database, schema, table: name, object_type: objectType })}`,
|
|
);
|
|
}
|
|
|
|
export async function getColumns(
|
|
connectionId: string,
|
|
database: string,
|
|
schema: string,
|
|
table: string,
|
|
): Promise<ColumnInfo[]> {
|
|
return get(`/api/schema/columns?${qs({ connection_id: connectionId, database, schema, table })}`);
|
|
}
|
|
|
|
export async function listIndexes(
|
|
connectionId: string,
|
|
database: string,
|
|
schema: string,
|
|
table: string,
|
|
): Promise<IndexInfo[]> {
|
|
return get(`/api/schema/indexes?${qs({ connection_id: connectionId, database, schema, table })}`);
|
|
}
|
|
|
|
export async function listForeignKeys(
|
|
connectionId: string,
|
|
database: string,
|
|
schema: string,
|
|
table: string,
|
|
): Promise<ForeignKeyInfo[]> {
|
|
return get(`/api/schema/foreign-keys?${qs({ connection_id: connectionId, database, schema, table })}`);
|
|
}
|
|
|
|
export async function listTriggers(
|
|
connectionId: string,
|
|
database: string,
|
|
schema: string,
|
|
table: string,
|
|
): Promise<TriggerInfo[]> {
|
|
return get(`/api/schema/triggers?${qs({ connection_id: connectionId, database, schema, table })}`);
|
|
}
|
|
|
|
export async function getTableDdl(
|
|
connectionId: string,
|
|
database: string,
|
|
schema: string,
|
|
table: string,
|
|
): Promise<string> {
|
|
return get(`/api/schema/ddl?${qs({ connection_id: connectionId, database, schema, table })}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Query
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function executeQuery(
|
|
connectionId: string,
|
|
database: string,
|
|
sql: string,
|
|
schema?: string,
|
|
executionId?: string,
|
|
options?: { maxRows?: number; fetchSize?: number; pageSize?: number; resultSessionId?: string },
|
|
): Promise<QueryResult> {
|
|
return post("/api/query/execute", { connectionId, database, sql, schema, executionId, ...options });
|
|
}
|
|
|
|
export async function executeMulti(
|
|
connectionId: string,
|
|
database: string,
|
|
sql: string,
|
|
schema?: string,
|
|
executionId?: string,
|
|
options?: { maxRows?: number; fetchSize?: number; pageSize?: number; resultSessionId?: string },
|
|
): Promise<QueryResult[]> {
|
|
return post("/api/query/execute-multi", { connectionId, database, sql, schema, executionId, ...options });
|
|
}
|
|
|
|
export async function closeQuerySession(connectionId: string, database: string, sessionId: string): Promise<boolean> {
|
|
return post("/api/query/close-session", { connectionId, database, sessionId });
|
|
}
|
|
|
|
export async function executeBatch(
|
|
connectionId: string,
|
|
database: string,
|
|
statements: string[],
|
|
schema?: string,
|
|
): Promise<QueryResult> {
|
|
return post("/api/query/execute-batch", { connectionId, database, statements, schema });
|
|
}
|
|
|
|
export async function executeScript(
|
|
connectionId: string,
|
|
database: string,
|
|
sql: string,
|
|
schema?: string,
|
|
): Promise<QueryResult> {
|
|
return post("/api/query/execute-script", { connectionId, database, sql, schema });
|
|
}
|
|
|
|
export async function executeInTransaction(
|
|
connectionId: string,
|
|
database: string,
|
|
statements: string[],
|
|
schema?: string,
|
|
): Promise<QueryResult> {
|
|
return post("/api/query/execute-in-transaction", { connectionId, database, statements, schema });
|
|
}
|
|
|
|
export async function cancelQuery(executionId: string): Promise<boolean> {
|
|
return post("/api/query/cancel", { executionId });
|
|
}
|
|
|
|
export async function analyzeSqlReferences(sql: string, dialect?: string): Promise<SqlReferenceAnalysis> {
|
|
return post("/api/query/analyze-sql-references", { sql, dialect });
|
|
}
|
|
|
|
export async function analyzeEditableQueryEditability(sql: string): Promise<QueryEditability> {
|
|
return post("/api/query/analyze-editability", { sql });
|
|
}
|
|
|
|
export async function prepareDataGridSave(options: DataGridSaveStatementOptions): Promise<DataGridSavePreparation> {
|
|
return post("/api/query/prepare-data-grid-save", { options });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// AI
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function aiComplete(request: AiCompletionRequest): Promise<string> {
|
|
return post("/api/ai/complete", { request });
|
|
}
|
|
|
|
export async function aiStream(
|
|
sessionId: string,
|
|
request: AiCompletionRequest,
|
|
onChunk: (chunk: AiStreamChunk) => void,
|
|
): Promise<void> {
|
|
const res = await fetch("/api/ai/stream", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ session_id: sessionId, request }),
|
|
});
|
|
if (!res.ok) throw new Error(await res.text());
|
|
|
|
const reader = res.body!.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = "";
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
buffer += decoder.decode(value, { stream: true });
|
|
|
|
const lines = buffer.split("\n");
|
|
buffer = lines.pop() || "";
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith("data:")) {
|
|
const data = line.slice(5).trim();
|
|
if (data && data !== "[DONE]") {
|
|
try {
|
|
const chunk: AiStreamChunk = JSON.parse(data);
|
|
onChunk(chunk);
|
|
if (chunk.done) return;
|
|
} catch {
|
|
// skip malformed JSON
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function aiCancelStream(sessionId: string): Promise<boolean> {
|
|
return post("/api/ai/cancel-stream", { sessionId });
|
|
}
|
|
|
|
export async function aiTestConnection(config: AiConfig): Promise<string> {
|
|
return post("/api/ai/test-connection", { config });
|
|
}
|
|
|
|
export async function aiListModels(config: AiConfig): Promise<AiModelInfo[]> {
|
|
return post("/api/ai/models", { config });
|
|
}
|
|
|
|
export async function saveAiConfig(config: AiConfig): Promise<void> {
|
|
return post("/api/ai/config", { config });
|
|
}
|
|
|
|
export async function loadAiConfig(): Promise<AiConfig | null> {
|
|
return get("/api/ai/config");
|
|
}
|
|
|
|
export async function loadDesktopSettings(): Promise<DesktopSettings> {
|
|
return { show_tray_icon: true };
|
|
}
|
|
|
|
export async function saveDesktopSettings(_settings: DesktopSettings): Promise<void> {
|
|
return;
|
|
}
|
|
|
|
export async function loadPinnedTreeNodeIds(): Promise<string[]> {
|
|
return get("/api/app-settings/pinned-tree-node-ids");
|
|
}
|
|
|
|
export async function savePinnedTreeNodeIds(_ids: string[]): Promise<void> {
|
|
return post("/api/app-settings/pinned-tree-node-ids", { ids: _ids });
|
|
}
|
|
|
|
// --- AI Conversations ---
|
|
|
|
export async function saveAiConversation(conversation: AiConversation): Promise<void> {
|
|
return post("/api/ai/conversation", { conversation });
|
|
}
|
|
|
|
export async function loadAiConversations(): Promise<AiConversation[]> {
|
|
return get("/api/ai/conversations");
|
|
}
|
|
|
|
export async function deleteAiConversation(id: string): Promise<void> {
|
|
return del(`/api/ai/conversation/${id}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SQL File Execution
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function previewSqlFile(fileOrPath: string | File): Promise<SqlFilePreview> {
|
|
if (typeof fileOrPath === "string") {
|
|
// In web mode a raw path is not useful; throw a clear error
|
|
throw new Error("previewSqlFile in web mode requires a File object, not a file path");
|
|
}
|
|
const formData = new FormData();
|
|
formData.append("file", fileOrPath);
|
|
const res = await fetch("/api/sql-file/preview", { method: "POST", body: formData });
|
|
if (!res.ok) throw new Error(await res.text());
|
|
return res.json();
|
|
}
|
|
|
|
export async function executeSqlFile(request: SqlFileRequest): Promise<void> {
|
|
return post("/api/sql-file/execute", { request });
|
|
}
|
|
|
|
export async function cancelSqlFileExecution(executionId: string): Promise<boolean> {
|
|
return post("/api/sql-file/cancel", { executionId });
|
|
}
|
|
|
|
export async function listenSqlFileProgress(_handler: (progress: SqlFileProgress) => void): Promise<() => void> {
|
|
// For HTTP mode we need an executionId, but the tauri API does not take one.
|
|
// The SSE endpoint requires a specific executionId. As a workaround we return
|
|
// a no-op unlisten; callers that need progress in web mode should use
|
|
// the web-specific SQL file progress listener instead.
|
|
return () => {};
|
|
}
|
|
|
|
export async function pendingOpenSqlFiles(): Promise<string[]> {
|
|
return [];
|
|
}
|
|
|
|
export async function pendingOpenConnectionLinks(): Promise<string[]> {
|
|
return [];
|
|
}
|
|
|
|
export async function readExternalSqlFile(_path: string): Promise<string> {
|
|
throw new Error("Opening external SQL file paths is only available in the desktop app");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Data Transfer
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function startTransfer(
|
|
request: TransferRequest,
|
|
onProgress: (progress: TransferProgress) => void,
|
|
): Promise<void> {
|
|
// 1. POST to start the transfer
|
|
const res = await fetch("/api/transfer/start", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ request }),
|
|
});
|
|
if (!res.ok) throw new Error(await res.text());
|
|
|
|
// 2. SSE to listen for progress
|
|
return new Promise((resolve, reject) => {
|
|
const es = new EventSource(`/api/transfer/progress/${request.transferId}`);
|
|
es.onmessage = (e) => {
|
|
const progress: TransferProgress = JSON.parse(e.data);
|
|
onProgress(progress);
|
|
if (progress.status === "done" || progress.status === "error" || progress.status === "cancelled") {
|
|
es.close();
|
|
resolve();
|
|
}
|
|
};
|
|
es.onerror = () => {
|
|
es.close();
|
|
reject(new Error("Transfer SSE connection failed"));
|
|
};
|
|
});
|
|
}
|
|
|
|
export async function cancelTransfer(transferId: string): Promise<void> {
|
|
return post("/api/transfer/cancel", { transferId });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Table File Import
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function previewTableImportFile(fileOrPath: string | File): Promise<TableImportPreview> {
|
|
if (typeof fileOrPath === "string") {
|
|
throw new Error("previewTableImportFile in web mode requires a File object, not a file path");
|
|
}
|
|
const formData = new FormData();
|
|
formData.append("file", fileOrPath);
|
|
const res = await fetch("/api/import/preview", { method: "POST", body: formData });
|
|
if (!res.ok) throw new Error(await res.text());
|
|
return res.json();
|
|
}
|
|
|
|
export async function importTableFile(
|
|
request: TableImportRequest,
|
|
onProgress: (progress: TableImportProgress) => void,
|
|
): Promise<TableImportSummary> {
|
|
// 1. POST to start the import
|
|
const res = await fetch("/api/import/execute", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ request }),
|
|
});
|
|
if (!res.ok) throw new Error(await res.text());
|
|
|
|
// 2. SSE to listen for progress
|
|
return new Promise((resolve, reject) => {
|
|
const es = new EventSource(`/api/import/progress/${request.importId}`);
|
|
let summary: TableImportSummary | null = null;
|
|
es.onmessage = (e) => {
|
|
const progress: TableImportProgress = JSON.parse(e.data);
|
|
onProgress(progress);
|
|
if (progress.status === "done") {
|
|
summary = {
|
|
importId: progress.importId,
|
|
rowsImported: progress.rowsImported,
|
|
totalRows: progress.totalRows,
|
|
};
|
|
es.close();
|
|
resolve(summary);
|
|
} else if (progress.status === "error" || progress.status === "cancelled") {
|
|
es.close();
|
|
reject(new Error(progress.error || "Import failed"));
|
|
}
|
|
};
|
|
es.onerror = () => {
|
|
es.close();
|
|
reject(new Error("Import SSE connection failed"));
|
|
};
|
|
});
|
|
}
|
|
|
|
export async function cancelTableImport(importId: string): Promise<boolean> {
|
|
return post("/api/import/cancel", { importId });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Database Export
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function exportDatabaseSql(
|
|
request: DatabaseExportRequest,
|
|
onProgress: (progress: ExportProgress) => void,
|
|
): Promise<void> {
|
|
// 1. POST to start the export
|
|
const res = await fetch("/api/export/database", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ request }),
|
|
});
|
|
if (!res.ok) throw new Error(await res.text());
|
|
|
|
// 2. SSE to listen for progress
|
|
return new Promise((resolve, reject) => {
|
|
const es = new EventSource(`/api/export/database/progress/${request.exportId}`);
|
|
es.onmessage = (e) => {
|
|
const progress: ExportProgress = JSON.parse(e.data);
|
|
onProgress(progress);
|
|
if (progress.status === "Done" || progress.status === "Error" || progress.status === "Cancelled") {
|
|
es.close();
|
|
resolve();
|
|
}
|
|
};
|
|
es.onerror = () => {
|
|
es.close();
|
|
reject(new Error("Export SSE connection failed"));
|
|
};
|
|
});
|
|
}
|
|
|
|
export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
|
await post("/api/export/database/cancel", { exportId });
|
|
}
|
|
|
|
export async function exportQueryResultCsv(
|
|
filePath: string,
|
|
columns: string[],
|
|
rows: readonly (readonly XlsxCellValue[])[],
|
|
): Promise<void> {
|
|
const { formatCsv } = await import("./exportFormats");
|
|
const content = formatCsv(columns, rows as (string | number | boolean | null)[][]);
|
|
const fileName = filePath.split(/[\\/]/).pop() || "export.csv";
|
|
const blob = new Blob(["\uFEFF", content], { type: "text/csv;charset=utf-8" });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = fileName;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
function downloadTextFile(filePath: string, fallbackFileName: string, content: string, mimeType: string): void {
|
|
const fileName = filePath.split(/[\\/]/).pop() || fallbackFileName;
|
|
const blob = new Blob(["\uFEFF", content], { type: mimeType });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = fileName;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
export async function exportQueryResultXlsx(
|
|
filePath: string,
|
|
sheetName: string | undefined,
|
|
columns: string[],
|
|
rows: readonly (readonly XlsxCellValue[])[],
|
|
): Promise<void> {
|
|
const { buildXlsxWorkbook } = await import("./xlsxExport");
|
|
const workbook = buildXlsxWorkbook({
|
|
sheetName: sheetName || "Export",
|
|
columns,
|
|
rows,
|
|
});
|
|
const fileName = filePath.split(/[\\/]/).pop() || "export.xlsx";
|
|
const blob = new Blob([workbook], {
|
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
});
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = fileName;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
export async function exportQueryResultJson(
|
|
filePath: string,
|
|
columns: string[],
|
|
rows: readonly (readonly XlsxCellValue[])[],
|
|
): Promise<void> {
|
|
const result = await post<{ content: string }>("/api/export/query-result-json", { columns, rows });
|
|
downloadTextFile(filePath, "export.json", result.content, "application/json;charset=utf-8");
|
|
}
|
|
|
|
export async function exportQueryResultMarkdown(
|
|
filePath: string,
|
|
columns: string[],
|
|
rows: readonly (readonly XlsxCellValue[])[],
|
|
): Promise<void> {
|
|
const result = await post<{ content: string }>("/api/export/query-result-markdown", { columns, rows });
|
|
downloadTextFile(filePath, "export.md", result.content, "text/markdown;charset=utf-8");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Redis
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function redisListDatabases(connectionId: string): Promise<RedisDatabaseInfo[]> {
|
|
return post("/api/redis/list-databases", { connectionId });
|
|
}
|
|
|
|
export async function redisScanKeys(
|
|
connectionId: string,
|
|
db: number,
|
|
cursor: number,
|
|
pattern: string,
|
|
count: number,
|
|
): Promise<RedisScanResult> {
|
|
return post("/api/redis/scan-keys", { connectionId, db, cursor, pattern, count });
|
|
}
|
|
|
|
export async function redisScanValues(
|
|
connectionId: string,
|
|
db: number,
|
|
cursor: number,
|
|
pattern: string,
|
|
query: string,
|
|
count: number,
|
|
): Promise<RedisScanResult> {
|
|
return post("/api/redis/scan-values", { connectionId, db, cursor, pattern, query, count });
|
|
}
|
|
|
|
export async function redisGetValue(connectionId: string, db: number, keyRaw: string): Promise<RedisValue> {
|
|
return post("/api/redis/get-value", { connectionId, db, keyRaw });
|
|
}
|
|
|
|
export async function redisSetString(
|
|
connectionId: string,
|
|
db: number,
|
|
keyRaw: string,
|
|
value: string,
|
|
ttl?: number,
|
|
): Promise<void> {
|
|
return post("/api/redis/set-string", { connectionId, db, keyRaw, value, ttl });
|
|
}
|
|
|
|
export async function redisDeleteKey(connectionId: string, db: number, keyRaw: string): Promise<void> {
|
|
return post("/api/redis/delete-key", { connectionId, db, keyRaw });
|
|
}
|
|
|
|
export async function redisHashSet(
|
|
connectionId: string,
|
|
db: number,
|
|
keyRaw: string,
|
|
field: string,
|
|
value: string,
|
|
): Promise<void> {
|
|
return post("/api/redis/hash-set", { connectionId, db, keyRaw, field, value });
|
|
}
|
|
|
|
export async function redisHashDel(connectionId: string, db: number, keyRaw: string, field: string): Promise<void> {
|
|
return post("/api/redis/hash-del", { connectionId, db, keyRaw, field });
|
|
}
|
|
|
|
export async function redisListPush(connectionId: string, db: number, keyRaw: string, value: string): Promise<void> {
|
|
return post("/api/redis/list-push", { connectionId, db, keyRaw, value });
|
|
}
|
|
|
|
export async function redisListSet(
|
|
connectionId: string,
|
|
db: number,
|
|
keyRaw: string,
|
|
index: number,
|
|
value: string,
|
|
): Promise<void> {
|
|
return post("/api/redis/list-set", { connectionId, db, keyRaw, index, value });
|
|
}
|
|
|
|
export async function redisListRemove(connectionId: string, db: number, keyRaw: string, index: number): Promise<void> {
|
|
return post("/api/redis/list-remove", { connectionId, db, keyRaw, index });
|
|
}
|
|
|
|
export async function redisSetAdd(connectionId: string, db: number, keyRaw: string, member: string): Promise<void> {
|
|
return post("/api/redis/set-add", { connectionId, db, keyRaw, member });
|
|
}
|
|
|
|
export async function redisSetRemove(connectionId: string, db: number, keyRaw: string, member: string): Promise<void> {
|
|
return post("/api/redis/set-remove", { connectionId, db, keyRaw, member });
|
|
}
|
|
|
|
export async function redisZadd(
|
|
connectionId: string,
|
|
db: number,
|
|
keyRaw: string,
|
|
member: string,
|
|
score: number,
|
|
): Promise<void> {
|
|
return post("/api/redis/zadd", { connectionId, db, keyRaw, member, score });
|
|
}
|
|
|
|
export async function redisZrem(connectionId: string, db: number, keyRaw: string, member: string): Promise<void> {
|
|
return post("/api/redis/zrem", { connectionId, db, keyRaw, member });
|
|
}
|
|
|
|
export async function redisSetTtl(connectionId: string, db: number, keyRaw: string, ttl: number): Promise<void> {
|
|
return post("/api/redis/set-ttl", { connectionId, db, keyRaw, ttl });
|
|
}
|
|
|
|
export async function redisDeleteKeys(connectionId: string, db: number, keyRaws: string[]): Promise<number> {
|
|
return post("/api/redis/delete-keys", { connectionId, db, keyRaws });
|
|
}
|
|
|
|
export async function redisFlushDb(connectionId: string, db: number): Promise<void> {
|
|
return post("/api/redis/flush-db", { connectionId, db });
|
|
}
|
|
|
|
export async function redisExecuteCommand(
|
|
connectionId: string,
|
|
db: number,
|
|
command: string,
|
|
): Promise<RedisCommandResult> {
|
|
return post("/api/redis/execute-command", { connectionId, db, command });
|
|
}
|
|
|
|
export async function redisLoadMore(
|
|
connectionId: string,
|
|
db: number,
|
|
keyRaw: string,
|
|
keyType: string,
|
|
cursor: number,
|
|
count: number,
|
|
): Promise<RedisValue> {
|
|
return post("/api/redis/load-more", { connectionId, db, keyRaw, keyType, cursor, count });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MongoDB
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function mongoListDatabases(connectionId: string): Promise<string[]> {
|
|
return post("/api/mongo/list-databases", { connectionId });
|
|
}
|
|
|
|
export async function mongoListCollections(connectionId: string, database: string): Promise<string[]> {
|
|
return post("/api/mongo/list-collections", { connectionId, database });
|
|
}
|
|
|
|
export async function mongoFindDocuments(
|
|
connectionId: string,
|
|
database: string,
|
|
collection: string,
|
|
skip: number,
|
|
limit: number,
|
|
filter?: string,
|
|
sort?: string,
|
|
): Promise<MongoDocumentResult> {
|
|
return post("/api/mongo/find-documents", { connectionId, database, collection, skip, limit, filter, sort });
|
|
}
|
|
|
|
export async function mongoInsertDocument(
|
|
connectionId: string,
|
|
database: string,
|
|
collection: string,
|
|
docJson: string,
|
|
): Promise<string> {
|
|
return post("/api/mongo/insert-document", { connectionId, database, collection, docJson });
|
|
}
|
|
|
|
export async function mongoUpdateDocument(
|
|
connectionId: string,
|
|
database: string,
|
|
collection: string,
|
|
id: string,
|
|
docJson: string,
|
|
): Promise<number> {
|
|
return post("/api/mongo/update-document", { connectionId, database, collection, id, docJson });
|
|
}
|
|
|
|
export async function mongoDeleteDocument(
|
|
connectionId: string,
|
|
database: string,
|
|
collection: string,
|
|
id: string,
|
|
): Promise<number> {
|
|
return post("/api/mongo/delete-document", { connectionId, database, collection, id });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// History
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function saveHistory(entry: HistoryEntry): Promise<void> {
|
|
return post("/api/history/save", { entry });
|
|
}
|
|
|
|
export async function loadHistory(limit: number, offset: number): Promise<HistoryEntry[]> {
|
|
return get(`/api/history?${qs({ limit, offset })}`);
|
|
}
|
|
|
|
export async function clearHistory(): Promise<void> {
|
|
return del("/api/history");
|
|
}
|
|
|
|
export async function deleteHistoryEntry(id: string): Promise<void> {
|
|
return del(`/api/history/${id}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Updates
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function checkForUpdates(): Promise<UpdateInfo> {
|
|
return get("/api/update/check");
|
|
}
|
|
|
|
export async function getAppVersion(): Promise<string> {
|
|
const res: { version: string } = await get("/api/version");
|
|
return res.version;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Layout
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function saveSidebarLayout(layout: SidebarLayout): Promise<void> {
|
|
return post("/api/layout/sidebar", { layout });
|
|
}
|
|
|
|
export async function loadSidebarLayout(): Promise<SidebarLayout | null> {
|
|
return get("/api/layout/sidebar");
|
|
}
|