mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-31 08:23:34 +00:00
perf: full-stack performance optimization
Frontend: - Debounce queryStore tab persistence (remove flush:sync deep watcher) - Eliminate unnecessary object spreading in connectionStore error handling - Use in-place delete for completion cache invalidation - Deduplicate schema cache prefix API calls - Split echarts, reka-ui, marked, @codemirror/commands into separate chunks - Lazy-load xlsxExport and markdownTable in DataGrid - Lazy-load marked parser in UpdateDialog Backend: - Replace tokio::sync::Mutex with RwLock for AppState connections/configs (60+ concurrent reads no longer block each other) - Parallelize PostgreSQL DDL metadata queries with tokio::try_join!
This commit is contained in:
@@ -74,8 +74,6 @@ import {
|
||||
} from "@/lib/gridSelection";
|
||||
import { buildTableSelectSql, quoteTableIdentifier } from "@/lib/tableSelectSql";
|
||||
import { buildDataGridRollbackStatements, buildDataGridSaveStatements, formatGridSqlLiteral } from "@/lib/dataGridSql";
|
||||
import { formatMarkdownTable } from "@/lib/markdownTable";
|
||||
import { buildXlsxWorkbook } from "@/lib/xlsxExport";
|
||||
import {
|
||||
matchesRowStatusFilter,
|
||||
rowStatusFilterAfterAddingRow,
|
||||
@@ -2019,6 +2017,7 @@ async function exportMarkdown() {
|
||||
try {
|
||||
const cols = props.result.columns;
|
||||
const visibleRows = displayItems.value.map((item) => item.data);
|
||||
const { formatMarkdownTable } = await import("@/lib/markdownTable");
|
||||
const md = formatMarkdownTable({ columns: cols, rows: visibleRows });
|
||||
if (await saveFileContent(md, "export.md", "Markdown", "md")) {
|
||||
toast(t("grid.exported"));
|
||||
@@ -2030,6 +2029,7 @@ async function exportMarkdown() {
|
||||
|
||||
async function exportXlsx() {
|
||||
try {
|
||||
const { buildXlsxWorkbook } = await import("@/lib/xlsxExport");
|
||||
const workbook = buildXlsxWorkbook({
|
||||
sheetName: props.tableMeta?.tableName || "Export",
|
||||
columns: props.result.columns,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Marked } from "marked";
|
||||
import { Loader2 } from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
@@ -27,12 +26,21 @@ const emit = defineEmits<{
|
||||
const { t } = useI18n();
|
||||
const isDesktop = isTauriRuntime();
|
||||
|
||||
const marked = new Marked({ breaks: true, gfm: true });
|
||||
const renderedNotes = ref("");
|
||||
|
||||
const renderedNotes = computed(() => {
|
||||
if (!props.updateInfo?.release_notes) return "";
|
||||
return marked.parse(props.updateInfo.release_notes) as string;
|
||||
});
|
||||
watch(
|
||||
() => props.updateInfo?.release_notes,
|
||||
async (notes) => {
|
||||
if (!notes) {
|
||||
renderedNotes.value = "";
|
||||
return;
|
||||
}
|
||||
const { Marked } = await import("marked");
|
||||
const marked = new Marked({ breaks: true, gfm: true });
|
||||
renderedNotes.value = marked.parse(notes) as string;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -121,17 +121,12 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
|
||||
function setConnectionError(connectionId: string, message: string) {
|
||||
connectionErrors.value = {
|
||||
...connectionErrors.value,
|
||||
[connectionId]: message,
|
||||
};
|
||||
connectionErrors.value[connectionId] = message;
|
||||
}
|
||||
|
||||
function clearConnectionError(connectionId: string) {
|
||||
if (!connectionErrors.value[connectionId]) return;
|
||||
const next = { ...connectionErrors.value };
|
||||
delete next[connectionId];
|
||||
connectionErrors.value = next;
|
||||
delete connectionErrors.value[connectionId];
|
||||
}
|
||||
|
||||
function recordConnectionError(connectionId: string, error: unknown): string {
|
||||
@@ -323,8 +318,15 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
loadedTreeNodeChildrenIds.value.delete(id);
|
||||
}
|
||||
}
|
||||
api.deleteSchemaCachePrefix(`${prefix}:`).catch(() => undefined);
|
||||
api.deleteSchemaCachePrefix(`${schemaCacheKey(prefix)}:`).catch(() => undefined);
|
||||
const rawPrefix = `${prefix}:`;
|
||||
const encodedPrefix = `${schemaCacheKey(prefix)}:`;
|
||||
if (rawPrefix === encodedPrefix) {
|
||||
api.deleteSchemaCachePrefix(rawPrefix).catch(() => undefined);
|
||||
} else {
|
||||
Promise.all([api.deleteSchemaCachePrefix(rawPrefix), api.deleteSchemaCachePrefix(encodedPrefix)]).catch(
|
||||
() => undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function schemaCachePrefixForNode(node: TreeNode): string | null {
|
||||
@@ -399,12 +401,12 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
function invalidateCompletionCache(connectionId: string) {
|
||||
const cachePrefix = `${connectionId}:`;
|
||||
completionTablesCache.value = Object.fromEntries(
|
||||
Object.entries(completionTablesCache.value).filter(([key]) => !key.startsWith(cachePrefix)),
|
||||
);
|
||||
completionColumnsCache.value = Object.fromEntries(
|
||||
Object.entries(completionColumnsCache.value).filter(([key]) => !key.startsWith(cachePrefix)),
|
||||
);
|
||||
for (const key of Object.keys(completionTablesCache.value)) {
|
||||
if (key.startsWith(cachePrefix)) delete completionTablesCache.value[key];
|
||||
}
|
||||
for (const key of Object.keys(completionColumnsCache.value)) {
|
||||
if (key.startsWith(cachePrefix)) delete completionColumnsCache.value[key];
|
||||
}
|
||||
}
|
||||
|
||||
async function removeConnection(id: string) {
|
||||
|
||||
@@ -39,7 +39,18 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const activeTabId = ref<string | null>(restored.activeTabId);
|
||||
const MAX_CACHED_RESULTS = 10;
|
||||
|
||||
watch([tabs, activeTabId], () => saveTabs(tabs.value, activeTabId.value), { deep: true, flush: "sync" });
|
||||
let _persistTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
watch(
|
||||
[tabs, activeTabId],
|
||||
() => {
|
||||
if (_persistTimer) clearTimeout(_persistTimer);
|
||||
_persistTimer = setTimeout(() => {
|
||||
saveTabs(tabs.value, activeTabId.value);
|
||||
_persistTimer = null;
|
||||
}, 300);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function findTabByTitle(connectionId: string, database: string, title: string) {
|
||||
return tabs.value.find((t) => t.connectionId === connectionId && t.database === database && t.title === title);
|
||||
|
||||
Reference in New Issue
Block a user