perf: full-stack performance and security hardening
Frontend performance: - Debounce DataGrid search (150ms) and replace O(n) row wrapping with index array - Replace QueryStore deep watcher with targeted field-level persistence snapshot - Extract shared nodeConfig computed in TreeItem (eliminates 15x redundant getConfig calls) - Clear tab result data on close to free memory - Add LRU eviction for completion caches (50 entry cap) - Replace savedSql version string join with numeric counter Memory leak fixes: - Store Tauri event unlisten handles and clean up on unmount - Clean up DataGrid search timer on unmount Backend security & resource management: - Add 50MB request body size limit - Reject file uploads >100MB and SQL files >200MB - Clean up SSE channels after transfer/import completes - Recover from poisoned mutex instead of panicking - Delete temporary upload files after preview - Validate SQL file paths to prevent directory traversal
This commit is contained in:
1 parent
741783445d
commit
f456ddd979
13 files changed
+230
-87
No files matched your search
@@ -984,6 +984,17 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return isSchemaAware(getConfig(connectionId)?.db_type);
|
||||
}
|
||||
|
||||
const COMPLETION_CACHE_MAX = 50;
|
||||
|
||||
function evictOldestCacheEntries(cache: Record<string, unknown>, max: number) {
|
||||
const keys = Object.keys(cache);
|
||||
if (keys.length <= max) return;
|
||||
const toRemove = keys.slice(0, keys.length - max);
|
||||
for (const key of toRemove) {
|
||||
delete cache[key];
|
||||
}
|
||||
}
|
||||
|
||||
async function listCompletionTables(connectionId: string, database: string): Promise<SqlCompletionTable[]> {
|
||||
const cacheKey = `${connectionId}:${database}`;
|
||||
if (completionTablesCache.value[cacheKey]) {
|
||||
@@ -1009,6 +1020,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}),
|
||||
);
|
||||
completionTablesCache.value[cacheKey] = tableGroups.flat();
|
||||
evictOldestCacheEntries(completionTablesCache.value, COMPLETION_CACHE_MAX);
|
||||
return completionTablesCache.value[cacheKey];
|
||||
}
|
||||
|
||||
@@ -1017,6 +1029,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
name: table.name,
|
||||
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
|
||||
}));
|
||||
evictOldestCacheEntries(completionTablesCache.value, COMPLETION_CACHE_MAX);
|
||||
return completionTablesCache.value[cacheKey];
|
||||
}
|
||||
|
||||
@@ -1031,6 +1044,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
await ensureConnected(connectionId);
|
||||
const querySchema = schema || database;
|
||||
completionColumnsCache.value[cacheKey] = await api.getColumns(connectionId, database, querySchema, table);
|
||||
evictOldestCacheEntries(completionColumnsCache.value, COMPLETION_CACHE_MAX);
|
||||
}
|
||||
|
||||
return completionColumnsCache.value[cacheKey].map((column) => ({
|
||||
|
||||
Reference in new issue
Block a user