diff --git a/src/components/editor/QueryToolbar.tsx b/src/components/editor/QueryToolbar.tsx index 7f95848..faa5880 100644 --- a/src/components/editor/QueryToolbar.tsx +++ b/src/components/editor/QueryToolbar.tsx @@ -16,7 +16,7 @@ import { import { useEffect, useRef, useState } from "react"; import { format } from "sql-formatter"; import { useQueryExecution } from "../../hooks/useQueryExecution"; -import { useSchemaCache } from "../../hooks/useSchemaCache"; +import { useSchemaCacheStore } from "../../stores/schemaCacheStore"; import { postProcessSQL } from "../../lib/sql-post-process"; import { useAiStore } from "../../stores/aiStore"; import { useConnectionStore } from "../../stores/connectionStore"; @@ -46,8 +46,8 @@ export function QueryToolbar() { (s) => s.selectedConnectionId, ); const activeConnections = useConnectionStore((s) => s.activeConnections); - const refreshSchema = useSchemaCache((s) => s.refreshSchema); - const schemaLoading = useSchemaCache((s) => s.loading); + const refreshSchema = useSchemaCacheStore((s) => s.refreshSchema); + const schemaLoading = useSchemaCacheStore((s) => s.loading); const { executeQuery, diff --git a/src/components/editor/SQLEditor.tsx b/src/components/editor/SQLEditor.tsx index e8b8dd2..bd9ee39 100644 --- a/src/components/editor/SQLEditor.tsx +++ b/src/components/editor/SQLEditor.tsx @@ -2,7 +2,7 @@ import Editor, { type OnMount, useMonaco } from "@monaco-editor/react"; import type { editor, IDisposable } from "monaco-editor"; import { useCallback, useEffect, useRef } from "react"; import { format } from "sql-formatter"; -import { useSchemaCache } from "../../hooks/useSchemaCache"; +import { useSchemaCacheStore } from "../../stores/schemaCacheStore"; import { createCompletionProvider } from "../../lib/schema-completion-provider"; import { postProcessSQL } from "../../lib/sql-post-process"; import { getStatementAtCursor } from "../../lib/statement-at-cursor"; @@ -47,7 +47,7 @@ export function SQLEditor() { const selectedConnectionId = useConnectionStore( (s) => s.selectedConnectionId, ); - const schemaCache = useSchemaCache(); + const schemaCache = useSchemaCacheStore(); // Sync schema cache with active connection useEffect(() => { @@ -274,7 +274,7 @@ export function SQLEditor() { 64, ], run: async () => { - await useSchemaCache.getState().refreshSchema(); + await useSchemaCacheStore.getState().refreshSchema(); }, }); diff --git a/src/components/editor/__tests__/QueryToolbar.test.tsx b/src/components/editor/__tests__/QueryToolbar.test.tsx index fdb4d87..54f369a 100644 --- a/src/components/editor/__tests__/QueryToolbar.test.tsx +++ b/src/components/editor/__tests__/QueryToolbar.test.tsx @@ -45,8 +45,8 @@ vi.mock("../../../hooks/useQueryExecution", () => ({ const mockRefreshSchema = vi.fn().mockResolvedValue(undefined); let mockSchemaLoading = false; -vi.mock("../../../hooks/useSchemaCache", () => ({ - useSchemaCache: vi.fn(() => ({ +vi.mock("../../../stores/schemaCacheStore", () => ({ + useSchemaCacheStore: vi.fn(() => ({ refreshSchema: mockRefreshSchema, loading: mockSchemaLoading, connectionId: "conn-1", diff --git a/src/components/editor/__tests__/SQLEditor.browser.test.tsx b/src/components/editor/__tests__/SQLEditor.browser.test.tsx index ffe0306..ddc5f66 100644 --- a/src/components/editor/__tests__/SQLEditor.browser.test.tsx +++ b/src/components/editor/__tests__/SQLEditor.browser.test.tsx @@ -156,8 +156,8 @@ vi.mock("../../stores/resultStore", () => ({ ), })); -vi.mock("../../hooks/useSchemaCache", () => ({ - useSchemaCache: vi.fn(() => ({ +vi.mock("../../stores/schemaCacheStore", () => ({ + useSchemaCacheStore: vi.fn(() => ({ connectionId: "conn-1", databases: [], tables: [], @@ -214,8 +214,8 @@ vi.mock("../../stores/themeStore", () => ({ }), })); -vi.mock("../../hooks/useSchemaCache", () => ({ - useSchemaCache: vi.fn((selector?: (s: object) => unknown) => { +vi.mock("../../stores/schemaCacheStore", () => ({ + useSchemaCacheStore: vi.fn((selector?: (s: object) => unknown) => { const state = { connectionId: null, databases: [], @@ -308,7 +308,7 @@ describe("SQLEditor (browser)", () => { expect(m.mockExecuteExplainAnalyze).toHaveBeenCalledWith("conn-1", "SELECT 1", undefined); }); - it("refresh-schema.run calls useSchemaCache.refreshSchema", async () => { + it("refresh-schema.run calls useSchemaCacheStore.refreshSchema", async () => { render(); await waitFor(() => { expect(m.capturedActions.some((a) => a.id === "refresh-schema")).toBe(true); diff --git a/src/components/editor/__tests__/SQLEditor.test.tsx b/src/components/editor/__tests__/SQLEditor.test.tsx index 45adf1c..03e9633 100644 --- a/src/components/editor/__tests__/SQLEditor.test.tsx +++ b/src/components/editor/__tests__/SQLEditor.test.tsx @@ -132,8 +132,8 @@ vi.mock("../../../stores/settingsStore", () => ({ })); // Mock schema cache store -vi.mock("../../../hooks/useSchemaCache", () => ({ - useSchemaCache: vi.fn((selector?: (s: any) => any) => { +vi.mock("../../../stores/schemaCacheStore", () => ({ + useSchemaCacheStore: vi.fn((selector?: (s: any) => any) => { const state = { connectionId: null, databases: [], diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index f936d53..56970f7 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -3,7 +3,7 @@ import { getCurrentWindow } from "@tauri-apps/api/window"; import { useCallback, useEffect, useState } from "react"; import { Group, Panel, Separator } from "react-resizable-panels"; import { useKeyboardShortcuts } from "../../hooks/useKeyboardShortcuts"; -import { useSchemaCache } from "../../hooks/useSchemaCache"; +import { useSchemaCacheStore } from "../../stores/schemaCacheStore"; import { useTheme } from "../../hooks/useTheme"; import { useAiStore } from "../../stores/aiStore"; import { useConnectionStore } from "../../stores/connectionStore"; @@ -140,7 +140,7 @@ export function AppLayout() { if (selectedConnectionId) disconnect(selectedConnectionId); break; case "refresh-schema": - useSchemaCache.getState().refreshSchema(); + useSchemaCacheStore.getState().refreshSchema(); break; case "compare-schemas": addCompareTab(); diff --git a/src/components/layout/__tests__/AppLayout.browser.test.tsx b/src/components/layout/__tests__/AppLayout.browser.test.tsx index d71f87e..1f6f01c 100644 --- a/src/components/layout/__tests__/AppLayout.browser.test.tsx +++ b/src/components/layout/__tests__/AppLayout.browser.test.tsx @@ -127,8 +127,8 @@ vi.mock("../../../hooks/useKeyboardShortcuts", () => ({ vi.mock("../../../hooks/useTheme", () => ({ useTheme: vi.fn(), })); -vi.mock("../../../hooks/useSchemaCache", () => ({ - useSchemaCache: { +vi.mock("../../../stores/schemaCacheStore", () => ({ + useSchemaCacheStore: { getState: vi.fn(() => ({ refreshSchema: vi.fn() })), }, })); @@ -652,8 +652,8 @@ describe("AppLayout (browser)", () => { // ─── Menu action: refresh-schema ─── it("handles refresh-schema menu action", async () => { const refreshSpy = vi.fn(); - const schemaCacheModule = await import("../../../hooks/useSchemaCache"); - vi.mocked(schemaCacheModule.useSchemaCache.getState).mockReturnValue({ refreshSchema: refreshSpy } as any); + const schemaCacheModule = await import("../../../stores/schemaCacheStore"); + vi.mocked(schemaCacheModule.useSchemaCacheStore.getState).mockReturnValue({ refreshSchema: refreshSpy } as any); await renderApp(); await act(async () => { window.dispatchEvent(new CustomEvent("menu-action", { detail: "refresh-schema" })); diff --git a/src/components/layout/__tests__/AppLayout.test.tsx b/src/components/layout/__tests__/AppLayout.test.tsx index 9729508..0485fe7 100644 --- a/src/components/layout/__tests__/AppLayout.test.tsx +++ b/src/components/layout/__tests__/AppLayout.test.tsx @@ -134,8 +134,8 @@ vi.mock("../../../hooks/useKeyboardShortcuts", () => ({ vi.mock("../../../hooks/useTheme", () => ({ useTheme: vi.fn(), })); -vi.mock("../../../hooks/useSchemaCache", () => ({ - useSchemaCache: { +vi.mock("../../../stores/schemaCacheStore", () => ({ + useSchemaCacheStore: { getState: vi.fn(() => ({ refreshSchema: vi.fn() })), }, })); diff --git a/src/hooks/__tests__/useSchemaCache.test.ts b/src/stores/__tests__/schemaCacheStore.test.ts similarity index 76% rename from src/hooks/__tests__/useSchemaCache.test.ts rename to src/stores/__tests__/schemaCacheStore.test.ts index 2468b5b..1082b17 100644 --- a/src/hooks/__tests__/useSchemaCache.test.ts +++ b/src/stores/__tests__/schemaCacheStore.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { useSchemaCache } from "../useSchemaCache"; +import { useSchemaCacheStore } from "../schemaCacheStore"; vi.mock("../../lib/tauri-api", () => ({ api: { @@ -23,7 +23,7 @@ const mockGetTriggers = api.getTriggers as ReturnType; const mockGetColumns = api.getColumns as ReturnType; function doReset() { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ connectionId: null, databases: [], tables: new Map(), @@ -43,17 +43,17 @@ beforeEach(() => { doReset(); }); -describe("useSchemaCache", () => { +describe("useSchemaCacheStore", () => { describe("setConnection", () => { it("clears all caches when setting a new connection", () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ databases: ["db1"], tables: new Map([["db1", ["table1"]]]), }); - useSchemaCache.getState().setConnection("conn-2"); + useSchemaCacheStore.getState().setConnection("conn-2"); - const state = useSchemaCache.getState(); + const state = useSchemaCacheStore.getState(); expect(state.connectionId).toBe("conn-2"); expect(state.databases).toEqual([]); expect(state.tables.size).toBe(0); @@ -64,14 +64,14 @@ describe("useSchemaCache", () => { }); it("does nothing when setting the same connection", () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ connectionId: "conn-1", databases: ["db1", "db2"], }); - useSchemaCache.getState().setConnection("conn-1"); + useSchemaCacheStore.getState().setConnection("conn-1"); - const state = useSchemaCache.getState(); + const state = useSchemaCacheStore.getState(); expect(state.databases).toEqual(["db1", "db2"]); }); @@ -82,27 +82,27 @@ describe("useSchemaCache", () => { ]); mockGetTables.mockResolvedValue([{ name: "users" }]); - useSchemaCache.getState().setConnection("conn-1"); + useSchemaCacheStore.getState().setConnection("conn-1"); await flushPromises(); expect(mockGetDatabases).toHaveBeenCalledWith("conn-1"); - expect(useSchemaCache.getState().databases).toEqual([ + expect(useSchemaCacheStore.getState().databases).toEqual([ "mydb", "testdb", ]); }); it("clears caches when connection is set to null", () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ connectionId: "conn-1", databases: ["db1"], tables: new Map([["db1", ["t1"]]]), }); - useSchemaCache.getState().setConnection(null); + useSchemaCacheStore.getState().setConnection(null); - const state = useSchemaCache.getState(); + const state = useSchemaCacheStore.getState(); expect(state.connectionId).toBeNull(); expect(state.databases).toEqual([]); expect(state.tables.size).toBe(0); @@ -111,12 +111,12 @@ describe("useSchemaCache", () => { describe("fetchDatabases", () => { it("returns cached databases if available and connectionId matches", async () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ connectionId: "conn-1", databases: ["cached_db"], }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchDatabases("conn-1"); @@ -131,15 +131,15 @@ describe("useSchemaCache", () => { ]); mockGetTables.mockResolvedValue([]); - useSchemaCache.setState({ connectionId: "conn-1" }); + useSchemaCacheStore.setState({ connectionId: "conn-1" }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchDatabases("conn-1"); expect(mockGetDatabases).toHaveBeenCalledWith("conn-1"); expect(result).toEqual(["db1", "db2"]); - expect(useSchemaCache.getState().databases).toEqual([ + expect(useSchemaCacheStore.getState().databases).toEqual([ "db1", "db2", ]); @@ -149,13 +149,13 @@ describe("useSchemaCache", () => { mockGetDatabases.mockResolvedValue([{ name: "db1" }]); mockGetTables.mockResolvedValue([]); - useSchemaCache.setState({ connectionId: "conn-1" }); + useSchemaCacheStore.setState({ connectionId: "conn-1" }); - const promise = useSchemaCache.getState().fetchDatabases("conn-1"); - expect(useSchemaCache.getState().loading).toBe(true); + const promise = useSchemaCacheStore.getState().fetchDatabases("conn-1"); + expect(useSchemaCacheStore.getState().loading).toBe(true); await promise; - expect(useSchemaCache.getState().loading).toBe(false); + expect(useSchemaCacheStore.getState().loading).toBe(false); }); it("pre-fetches tables for all databases", async () => { @@ -165,9 +165,9 @@ describe("useSchemaCache", () => { ]); mockGetTables.mockResolvedValue([{ name: "users" }]); - useSchemaCache.setState({ connectionId: "conn-1" }); + useSchemaCacheStore.setState({ connectionId: "conn-1" }); - await useSchemaCache.getState().fetchDatabases("conn-1"); + await useSchemaCacheStore.getState().fetchDatabases("conn-1"); expect(mockGetTables).toHaveBeenCalledWith("conn-1", "db1"); expect(mockGetTables).toHaveBeenCalledWith("conn-1", "db2"); @@ -176,24 +176,24 @@ describe("useSchemaCache", () => { it("returns empty array and sets loading false on error", async () => { mockGetDatabases.mockRejectedValue(new Error("Network error")); - useSchemaCache.setState({ connectionId: "conn-1" }); + useSchemaCacheStore.setState({ connectionId: "conn-1" }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchDatabases("conn-1"); expect(result).toEqual([]); - expect(useSchemaCache.getState().loading).toBe(false); + expect(useSchemaCacheStore.getState().loading).toBe(false); }); }); describe("fetchTables", () => { it("returns cached tables if available", async () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ tables: new Map([["mydb", ["users", "orders"]]]), }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchTables("conn-1", "mydb"); @@ -207,13 +207,13 @@ describe("useSchemaCache", () => { { name: "orders" }, ]); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchTables("conn-1", "mydb"); expect(mockGetTables).toHaveBeenCalledWith("conn-1", "mydb"); expect(result).toEqual(["users", "orders"]); - expect(useSchemaCache.getState().tables.get("mydb")).toEqual([ + expect(useSchemaCacheStore.getState().tables.get("mydb")).toEqual([ "users", "orders", ]); @@ -222,22 +222,22 @@ describe("useSchemaCache", () => { it("returns empty array on error", async () => { mockGetTables.mockRejectedValue(new Error("DB error")); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchTables("conn-1", "mydb"); expect(result).toEqual([]); - expect(useSchemaCache.getState().loading).toBe(false); + expect(useSchemaCacheStore.getState().loading).toBe(false); }); }); describe("fetchViews", () => { it("returns cached views if available", async () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ views: new Map([["mydb", ["user_view"]]]), }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchViews("conn-1", "mydb"); @@ -248,13 +248,13 @@ describe("useSchemaCache", () => { it("fetches from API when not cached", async () => { mockGetViews.mockResolvedValue([{ name: "v1" }, { name: "v2" }]); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchViews("conn-1", "mydb"); expect(mockGetViews).toHaveBeenCalledWith("conn-1", "mydb"); expect(result).toEqual(["v1", "v2"]); - expect(useSchemaCache.getState().views.get("mydb")).toEqual([ + expect(useSchemaCacheStore.getState().views.get("mydb")).toEqual([ "v1", "v2", ]); @@ -263,7 +263,7 @@ describe("useSchemaCache", () => { it("returns empty array on error", async () => { mockGetViews.mockRejectedValue(new Error("DB error")); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchViews("conn-1", "mydb"); @@ -273,11 +273,11 @@ describe("useSchemaCache", () => { describe("fetchRoutines", () => { it("returns cached routines if available", async () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ routines: new Map([["mydb", ["proc1", "func1"]]]), }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchRoutines("conn-1", "mydb"); @@ -291,21 +291,21 @@ describe("useSchemaCache", () => { { name: "my_func" }, ]); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchRoutines("conn-1", "mydb"); expect(mockGetRoutines).toHaveBeenCalledWith("conn-1", "mydb"); expect(result).toEqual(["my_proc", "my_func"]); expect( - useSchemaCache.getState().routines.get("mydb"), + useSchemaCacheStore.getState().routines.get("mydb"), ).toEqual(["my_proc", "my_func"]); }); it("returns empty array on error", async () => { mockGetRoutines.mockRejectedValue(new Error("DB error")); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchRoutines("conn-1", "mydb"); @@ -315,11 +315,11 @@ describe("useSchemaCache", () => { describe("fetchTriggers", () => { it("returns cached triggers if available", async () => { - useSchemaCache.setState({ + useSchemaCacheStore.setState({ triggers: new Map([["mydb", ["trg1"]]]), }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchTriggers("conn-1", "mydb"); @@ -332,21 +332,21 @@ describe("useSchemaCache", () => { { name: "trg_before_insert" }, ]); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchTriggers("conn-1", "mydb"); expect(mockGetTriggers).toHaveBeenCalledWith("conn-1", "mydb"); expect(result).toEqual(["trg_before_insert"]); expect( - useSchemaCache.getState().triggers.get("mydb"), + useSchemaCacheStore.getState().triggers.get("mydb"), ).toEqual(["trg_before_insert"]); }); it("returns empty array on error", async () => { mockGetTriggers.mockRejectedValue(new Error("DB error")); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchTriggers("conn-1", "mydb"); @@ -359,11 +359,11 @@ describe("useSchemaCache", () => { const colInfo = [ { name: "id", dataType: "int", nullable: false }, ] as ColumnInfo[]; - useSchemaCache.setState({ + useSchemaCacheStore.setState({ columns: new Map([["mydb.users", colInfo]]), }); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchColumns("conn-1", "mydb", "users"); @@ -377,21 +377,21 @@ describe("useSchemaCache", () => { ] as ColumnInfo[]; mockGetColumns.mockResolvedValue(colInfo); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchColumns("conn-1", "mydb", "users"); expect(mockGetColumns).toHaveBeenCalledWith("conn-1", "mydb", "users"); expect(result).toEqual(colInfo); expect( - useSchemaCache.getState().columns.get("mydb.users"), + useSchemaCacheStore.getState().columns.get("mydb.users"), ).toEqual(colInfo); }); it("returns empty array on error", async () => { mockGetColumns.mockRejectedValue(new Error("DB error")); - const result = await useSchemaCache + const result = await useSchemaCacheStore .getState() .fetchColumns("conn-1", "mydb", "users"); @@ -406,7 +406,7 @@ describe("useSchemaCache", () => { ]); mockGetTables.mockResolvedValue([]); - useSchemaCache.setState({ + useSchemaCacheStore.setState({ connectionId: "conn-1", databases: ["old_db"], tables: new Map([["old_db", ["old_table"]]]), @@ -416,9 +416,9 @@ describe("useSchemaCache", () => { columns: new Map([["old_db.old_table", []]]), }); - await useSchemaCache.getState().refreshSchema(); + await useSchemaCacheStore.getState().refreshSchema(); - const state = useSchemaCache.getState(); + const state = useSchemaCacheStore.getState(); expect(state.databases).toEqual(["new_db"]); // After refresh, fetchDatabases eagerly fetches tables for all new databases, // so tables Map has an entry for "new_db" (even if empty result). @@ -431,9 +431,9 @@ describe("useSchemaCache", () => { }); it("does nothing when connectionId is null", async () => { - useSchemaCache.setState({ connectionId: null }); + useSchemaCacheStore.setState({ connectionId: null }); - await useSchemaCache.getState().refreshSchema(); + await useSchemaCacheStore.getState().refreshSchema(); expect(mockGetDatabases).not.toHaveBeenCalled(); }); diff --git a/src/hooks/useSchemaCache.ts b/src/stores/schemaCacheStore.ts similarity index 98% rename from src/hooks/useSchemaCache.ts rename to src/stores/schemaCacheStore.ts index f000cad..5e54249 100644 --- a/src/hooks/useSchemaCache.ts +++ b/src/stores/schemaCacheStore.ts @@ -26,7 +26,7 @@ interface SchemaCache { refreshSchema: () => Promise; } -export const useSchemaCache = create((set, get) => ({ +export const useSchemaCacheStore = create((set, get) => ({ connectionId: null, databases: [], tables: new Map(),