mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-01 00:25:38 +00:00
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
182 lines
5.2 KiB
TypeScript
182 lines
5.2 KiB
TypeScript
import { defineStore } from "pinia";
|
|
import { ref } from "vue";
|
|
import { uuid } from "@/lib/utils";
|
|
import * as api from "@/lib/api";
|
|
import type { SavedSqlFile, SavedSqlFolder, SavedSqlLibrary } from "@/types/database";
|
|
|
|
const LEGACY_STORAGE_KEY = "dbx-saved-sql-library";
|
|
|
|
interface SavedSqlState {
|
|
folders: SavedSqlFolder[];
|
|
files: SavedSqlFile[];
|
|
}
|
|
|
|
function nowIso() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function loadLegacyState(): SavedSqlState {
|
|
try {
|
|
const raw = localStorage.getItem(LEGACY_STORAGE_KEY);
|
|
if (!raw) return { folders: [], files: [] };
|
|
const parsed = JSON.parse(raw) as Partial<SavedSqlState>;
|
|
return {
|
|
folders: Array.isArray(parsed.folders) ? parsed.folders.filter((item) => item?.id && item?.connectionId) : [],
|
|
files: Array.isArray(parsed.files) ? parsed.files.filter((item) => item?.id && item?.connectionId) : [],
|
|
};
|
|
} catch {
|
|
return { folders: [], files: [] };
|
|
}
|
|
}
|
|
|
|
export const useSavedSqlStore = defineStore("savedSql", () => {
|
|
const folders = ref<SavedSqlFolder[]>([]);
|
|
const files = ref<SavedSqlFile[]>([]);
|
|
const isLoaded = ref(false);
|
|
|
|
const version = ref(0);
|
|
function bumpVersion() {
|
|
version.value++;
|
|
}
|
|
|
|
function applyLibrary(library: SavedSqlLibrary) {
|
|
folders.value = library.folders;
|
|
files.value = library.files;
|
|
bumpVersion();
|
|
}
|
|
|
|
async function migrateLegacyLocalStorage() {
|
|
const legacy = loadLegacyState();
|
|
if (legacy.folders.length === 0 && legacy.files.length === 0) return;
|
|
|
|
for (const folder of legacy.folders) {
|
|
await api.saveSavedSqlFolder(folder);
|
|
}
|
|
for (const file of legacy.files) {
|
|
await api.saveSavedSqlFile(file);
|
|
}
|
|
localStorage.removeItem(LEGACY_STORAGE_KEY);
|
|
}
|
|
|
|
async function initFromStorage() {
|
|
await migrateLegacyLocalStorage();
|
|
applyLibrary(await api.loadSavedSqlLibrary());
|
|
isLoaded.value = true;
|
|
}
|
|
|
|
function listFolders(connectionId: string) {
|
|
return folders.value
|
|
.filter((folder) => folder.connectionId === connectionId)
|
|
.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: "base" }));
|
|
}
|
|
|
|
function listFiles(connectionId: string, folderId?: string) {
|
|
return files.value
|
|
.filter((file) => file.connectionId === connectionId && (file.folderId || "") === (folderId || ""))
|
|
.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: "base" }));
|
|
}
|
|
|
|
function getFile(id: string) {
|
|
return files.value.find((file) => file.id === id);
|
|
}
|
|
|
|
async function createFolder(connectionId: string, name: string) {
|
|
const timestamp = nowIso();
|
|
const folder: SavedSqlFolder = {
|
|
id: uuid(),
|
|
connectionId,
|
|
name,
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
};
|
|
const saved = await api.saveSavedSqlFolder(folder);
|
|
folders.value = [...folders.value.filter((item) => item.id !== saved.id), saved];
|
|
bumpVersion();
|
|
return saved;
|
|
}
|
|
|
|
async function renameFolder(id: string, name: string) {
|
|
const existing = folders.value.find((folder) => folder.id === id);
|
|
if (!existing) return;
|
|
const saved = await api.saveSavedSqlFolder({ ...existing, name, updatedAt: nowIso() });
|
|
folders.value = folders.value.map((folder) => (folder.id === id ? saved : folder));
|
|
bumpVersion();
|
|
}
|
|
|
|
async function deleteFolder(id: string) {
|
|
await api.deleteSavedSqlFolder(id);
|
|
folders.value = folders.value.filter((folder) => folder.id !== id);
|
|
files.value = files.value.filter((file) => file.folderId !== id);
|
|
bumpVersion();
|
|
}
|
|
|
|
async function saveFile(input: {
|
|
id?: string;
|
|
connectionId: string;
|
|
folderId?: string;
|
|
name: string;
|
|
database: string;
|
|
schema?: string;
|
|
sql: string;
|
|
}) {
|
|
const timestamp = nowIso();
|
|
const existing = input.id ? getFile(input.id) : undefined;
|
|
const file: SavedSqlFile = existing
|
|
? {
|
|
...existing,
|
|
folderId: input.folderId || undefined,
|
|
name: input.name,
|
|
database: input.database,
|
|
schema: input.schema,
|
|
sql: input.sql,
|
|
updatedAt: timestamp,
|
|
}
|
|
: {
|
|
id: uuid(),
|
|
connectionId: input.connectionId,
|
|
folderId: input.folderId || undefined,
|
|
name: input.name,
|
|
database: input.database,
|
|
schema: input.schema,
|
|
sql: input.sql,
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
};
|
|
const saved = await api.saveSavedSqlFile(file);
|
|
files.value = [...files.value.filter((item) => item.id !== saved.id), saved];
|
|
bumpVersion();
|
|
return saved;
|
|
}
|
|
|
|
async function renameFile(id: string, name: string) {
|
|
const existing = getFile(id);
|
|
if (!existing) return;
|
|
const saved = await api.saveSavedSqlFile({ ...existing, name, updatedAt: nowIso() });
|
|
files.value = files.value.map((file) => (file.id === id ? saved : file));
|
|
bumpVersion();
|
|
}
|
|
|
|
async function deleteFile(id: string) {
|
|
await api.deleteSavedSqlFile(id);
|
|
files.value = files.value.filter((file) => file.id !== id);
|
|
bumpVersion();
|
|
}
|
|
|
|
return {
|
|
folders,
|
|
files,
|
|
isLoaded,
|
|
version,
|
|
initFromStorage,
|
|
listFolders,
|
|
listFiles,
|
|
getFile,
|
|
createFolder,
|
|
renameFolder,
|
|
deleteFolder,
|
|
saveFile,
|
|
renameFile,
|
|
deleteFile,
|
|
};
|
|
});
|