From 4f2e4ac24d3165283cae8ed03d35c648d18b090c Mon Sep 17 00:00:00 2001 From: JayceeB1 <45721154+JayceeB1@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:43:32 +0100 Subject: [PATCH 1/3] Fix server-mode repo switching in gitnexus-web --- gitnexus-web/src/App.tsx | 9 ++- gitnexus-web/src/core/lbug/lbug-adapter.ts | 30 ++++++-- gitnexus-web/src/hooks/useAppState.tsx | 16 +++- gitnexus-web/src/types/lbug-wasm.d.ts | 4 +- gitnexus-web/src/workers/ingestion.worker.ts | 80 ++++++++++---------- 5 files changed, 84 insertions(+), 55 deletions(-) diff --git a/gitnexus-web/src/App.tsx b/gitnexus-web/src/App.tsx index 72b78a5d9..64cb5fe39 100644 --- a/gitnexus-web/src/App.tsx +++ b/gitnexus-web/src/App.tsx @@ -26,6 +26,7 @@ const AppContent = () => { isRightPanelOpen, runPipeline, runPipelineFromFiles, + hydrateServerGraph, isSettingsPanelOpen, setSettingsPanelOpen, refreshLLMSettings, @@ -133,7 +134,7 @@ const AppContent = () => { } }, [setViewMode, setGraph, setFileContents, setProgress, setProjectName, runPipelineFromFiles, startEmbeddings, initializeAgent]); - const handleServerConnect = useCallback((result: ConnectToServerResult) => { + const handleServerConnect = useCallback(async (result: ConnectToServerResult) => { // Extract project name from repoPath const repoPath = result.repoInfo.repoPath; const projectName = repoPath.split('/').pop() || 'server-project'; @@ -156,6 +157,8 @@ const AppContent = () => { } setFileContents(fileMap); + await hydrateServerGraph(result); + // Transition directly to exploring view setViewMode('exploring'); setProgress(null); @@ -182,7 +185,7 @@ const AppContent = () => { initializeAgent(projectName); } }); - }, [setViewMode, setGraph, setFileContents, setProjectName, setProgress, initializeAgent, startEmbeddings, hydrateWorkerFromServer]); + }, [setViewMode, setGraph, setFileContents, setProjectName, setProgress, initializeAgent, startEmbeddings, hydrateServerGraph, hydrateWorkerFromServer]); // Auto-connect when ?server query param is present (bookmarkable shortcut) const autoConnectRan = useRef(false); @@ -214,7 +217,7 @@ const AppContent = () => { setProgress({ phase: 'extracting', percent: 97, message: 'Processing...', detail: 'Extracting file contents' }); } }).then(async (result) => { - handleServerConnect(result); + await handleServerConnect(result); // Store server URL and fetch available repos for the repo switcher setServerBaseUrl(baseUrl); diff --git a/gitnexus-web/src/core/lbug/lbug-adapter.ts b/gitnexus-web/src/core/lbug/lbug-adapter.ts index 916d4dacc..9f868580a 100644 --- a/gitnexus-web/src/core/lbug/lbug-adapter.ts +++ b/gitnexus-web/src/core/lbug/lbug-adapter.ts @@ -190,7 +190,7 @@ export const loadGraphToLbug = async ( for (const tableName of NODE_TABLES) { try { const countRes = await conn.query(`MATCH (n:${tableName}) RETURN count(n) AS cnt`); - const countRows = await (countRes.getAll?.() ?? countRes.getAllObjects?.() ?? countRes.getAllRows?.() ?? []); + const countRows = await getQueryRows(countRes); const countRow = countRows[0]; const count = countRow ? (countRow.cnt ?? countRow[0] ?? 0) : 0; totalNodes += Number(count); @@ -230,6 +230,20 @@ const escapeTableName = (table: string): string => { return BACKTICK_TABLES.has(table) ? `\`${table}\`` : table; }; +const getQueryRows = async (result: any): Promise => { + if (!result) return []; + if (typeof result.getAllObjects === 'function') { + return await result.getAllObjects(); + } + if (typeof result.getAllRows === 'function') { + return await result.getAllRows(); + } + if (typeof result.getAll === 'function') { + return await result.getAll(); + } + throw new Error('Unsupported LadybugDB QueryResult shape'); +}; + /** Tables with isExported column (TypeScript/JS-native types) */ const TABLES_WITH_EXPORTED = new Set(['Function', 'Class', 'Interface', 'Method', 'CodeElement']); @@ -293,8 +307,8 @@ export const executeQuery = async (cypher: string): Promise => { }); } - // Collect all rows (handle API differences across LadybugDB versions) - const allRows = await (result.getAll?.() ?? result.getAllObjects?.() ?? result.getAllRows?.() ?? []); + // Collect all rows + const allRows = await getQueryRows(result); const rows: any[] = []; for (const row of allRows) { // Convert tuple to named object if we have column names and row is array @@ -331,7 +345,7 @@ export const getLbugStats = async (): Promise<{ nodes: number; edges: number }> for (const tableName of NODE_TABLES) { try { const nodeResult = await conn.query(`MATCH (n:${tableName}) RETURN count(n) AS cnt`); - const nodeRows = await (nodeResult.getAll?.() ?? nodeResult.getAllObjects?.() ?? nodeResult.getAllRows?.() ?? []); + const nodeRows = await getQueryRows(nodeResult); const nodeRow = nodeRows[0]; totalNodes += Number(nodeRow?.cnt ?? nodeRow?.[0] ?? 0); } catch { @@ -343,7 +357,7 @@ export const getLbugStats = async (): Promise<{ nodes: number; edges: number }> let totalEdges = 0; try { const edgeResult = await conn.query(`MATCH ()-[r:${REL_TABLE_NAME}]->() RETURN count(r) AS cnt`); - const edgeRows = await (edgeResult.getAll?.() ?? edgeResult.getAllObjects?.() ?? edgeResult.getAllRows?.() ?? []); + const edgeRows = await getQueryRows(edgeResult); const edgeRow = edgeRows[0]; totalEdges = Number(edgeRow?.cnt ?? edgeRow?.[0] ?? 0); } catch { @@ -408,7 +422,7 @@ export const executePrepared = async ( const result = await conn.execute(stmt, params); - const rows = await (result.getAll?.() ?? result.getAllObjects?.() ?? result.getAllRows?.() ?? []); + const rows = await getQueryRows(result); await stmt.close(); return rows; @@ -472,7 +486,7 @@ export const testArrayParams = async (): Promise<{ success: boolean; error?: str for (const tableName of NODE_TABLES) { try { const nodeResult = await conn.query(`MATCH (n:${tableName}) RETURN n.id AS id LIMIT 1`); - const nodeRows = await (nodeResult.getAll?.() ?? nodeResult.getAllObjects?.() ?? nodeResult.getAllRows?.() ?? []); + const nodeRows = await getQueryRows(nodeResult); const nodeRow = nodeRows[0]; if (nodeRow) { testNodeId = nodeRow.id ?? nodeRow[0]; @@ -509,7 +523,7 @@ export const testArrayParams = async (): Promise<{ success: boolean; error?: str const verifyResult = await conn.query( `MATCH (e:${EMBEDDING_TABLE_NAME} {nodeId: '${testNodeId}'}) RETURN e.embedding AS emb` ); - const verifyRows = await (verifyResult.getAll?.() ?? verifyResult.getAllObjects?.() ?? verifyResult.getAllRows?.() ?? []); + const verifyRows = await getQueryRows(verifyResult); const verifyRow = verifyRows[0]; const storedEmb = verifyRow?.emb ?? verifyRow?.[0]; diff --git a/gitnexus-web/src/hooks/useAppState.tsx b/gitnexus-web/src/hooks/useAppState.tsx index 843f967d7..08dc15f2c 100644 --- a/gitnexus-web/src/hooks/useAppState.tsx +++ b/gitnexus-web/src/hooks/useAppState.tsx @@ -123,6 +123,7 @@ interface AppState { // Worker API (shared across app) runPipeline: (file: File, onProgress: (p: PipelineProgress) => void, clusteringConfig?: ProviderConfig) => Promise; runPipelineFromFiles: (files: FileEntry[], onProgress: (p: PipelineProgress) => void, clusteringConfig?: ProviderConfig) => Promise; + hydrateServerGraph: (result: ConnectToServerResult) => Promise; runQuery: (cypher: string) => Promise; isDatabaseReady: () => Promise; hydrateWorkerFromServer: (nodes: any[], relationships: any[], fileContents: Record) => Promise; @@ -467,6 +468,16 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { return deserializePipelineResult(serializedResult, createKnowledgeGraph); }, []); + const hydrateServerGraph = useCallback(async (result: ConnectToServerResult): Promise => { + const api = apiRef.current; + if (!api) throw new Error('Worker not initialized'); + await api.hydrateServerGraph({ + nodes: result.nodes, + relationships: result.relationships, + fileContents: result.fileContents, + }); + }, []); + const runQuery = useCallback(async (cypher: string): Promise => { const api = apiRef.current; if (!api) throw new Error('Worker not initialized'); @@ -1028,6 +1039,8 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { for (const [p, c] of Object.entries(result.fileContents)) fileMap.set(p, c); setFileContents(fileMap); + await hydrateServerGraph(result); + setViewMode('exploring'); setProgress(null); @@ -1056,7 +1069,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { }); setTimeout(() => { setViewMode('exploring'); setProgress(null); }, 3000); } - }, [serverBaseUrl, setProgress, setViewMode, setProjectName, setGraph, setFileContents, initializeAgent, startEmbeddings, hydrateWorkerFromServer, setHighlightedNodeIds, clearAIToolHighlights, clearBlastRadius, setSelectedNode, setQueryResult, setCodeReferences, setCodePanelOpen, setCodeReferenceFocus]); + }, [serverBaseUrl, setProgress, setViewMode, setProjectName, setGraph, setFileContents, initializeAgent, startEmbeddings, hydrateServerGraph, hydrateWorkerFromServer, setHighlightedNodeIds, clearAIToolHighlights, clearBlastRadius, setSelectedNode, setQueryResult, setCodeReferences, setCodePanelOpen, setCodeReferenceFocus]); const removeCodeReference = useCallback((id: string) => { setCodeReferences(prev => { @@ -1159,6 +1172,7 @@ export const AppStateProvider = ({ children }: { children: ReactNode }) => { switchRepo, runPipeline, runPipelineFromFiles, + hydrateServerGraph, runQuery, isDatabaseReady, hydrateWorkerFromServer, diff --git a/gitnexus-web/src/types/lbug-wasm.d.ts b/gitnexus-web/src/types/lbug-wasm.d.ts index 3e4d71486..669c61139 100644 --- a/gitnexus-web/src/types/lbug-wasm.d.ts +++ b/gitnexus-web/src/types/lbug-wasm.d.ts @@ -12,7 +12,9 @@ declare module '@ladybugdb/wasm-core' { close(): Promise; } export interface QueryResult { - getAll(): Promise; + getAll?(): Promise; + getAllRows?(): Promise; + getAllObjects?(): Promise; hasNext(): Promise; getNext(): Promise; } diff --git a/gitnexus-web/src/workers/ingestion.worker.ts b/gitnexus-web/src/workers/ingestion.worker.ts index 54974ca56..48ae0b062 100644 --- a/gitnexus-web/src/workers/ingestion.worker.ts +++ b/gitnexus-web/src/workers/ingestion.worker.ts @@ -56,6 +56,29 @@ let enrichmentCancelled = false; // Chat cancellation flag let chatCancelled = false; +const loadResultIntoWorkerState = async (result: PipelineResult): Promise => { + currentGraphResult = result; + storedFileContents = result.fileContents; + + const bm25DocCount = buildBM25Index(storedFileContents); + if (import.meta.env.DEV) { + console.log(`🔍 BM25 index built: ${bm25DocCount} documents`); + } + + try { + const lbug = await getLbugAdapter(); + await lbug.loadGraphToLbug(result.graph, result.fileContents); + + if (import.meta.env.DEV) { + const stats = await lbug.getLbugStats(); + console.log('LadybugDB loaded:', stats); + console.log('📁 Stored', storedFileContents.size, 'files for grep/read tools'); + } + } catch { + // LadybugDB is optional - silently continue without it + } +}; + // ============================================================ // HTTP helpers for backend mode // ============================================================ @@ -163,18 +186,7 @@ const workerApi = { console.log('🔧 runPipeline called with clusteringConfig:', !!clusteringConfig); // Run the actual pipeline const result = await runIngestionPipeline(file, onProgress); - currentGraphResult = result; - - // Store file contents for grep/read tools (full content, not truncated) - storedFileContents = result.fileContents; - - // Build BM25 index for keyword search (instant, ~100ms) - const bm25DocCount = buildBM25Index(storedFileContents); - if (import.meta.env.DEV) { - console.log(`🔍 BM25 index built: ${bm25DocCount} documents`); - } - - // Load graph into LadybugDB for querying (optional - gracefully degrades) + // Load graph into local indexes for query/AI features. try { onProgress({ phase: 'complete', @@ -186,15 +198,7 @@ const workerApi = { nodesCreated: result.graph.nodeCount, }, }); - - const lbug = await getLbugAdapter(); - await lbug.loadGraphToLbug(result.graph, result.fileContents); - - if (import.meta.env.DEV) { - const stats = await lbug.getLbugStats(); - console.log('LadybugDB loaded:', stats); - console.log('📁 Stored', storedFileContents.size, 'files for grep/read tools'); - } + await loadResultIntoWorkerState(result); } catch { // LadybugDB is optional - silently continue without it } @@ -311,18 +315,7 @@ const workerApi = { // Run the pipeline const result = await runPipelineFromFiles(files, onProgress); - currentGraphResult = result; - - // Store file contents for grep/read tools (full content, not truncated) - storedFileContents = result.fileContents; - - // Build BM25 index for keyword search (instant, ~100ms) - const bm25DocCount = buildBM25Index(storedFileContents); - if (import.meta.env.DEV) { - console.log(`🔍 BM25 index built: ${bm25DocCount} documents`); - } - - // Load graph into LadybugDB for querying (optional - gracefully degrades) + // Load graph into local indexes for query/AI features. try { onProgress({ phase: 'complete', @@ -334,15 +327,7 @@ const workerApi = { nodesCreated: result.graph.nodeCount, }, }); - - const lbug = await getLbugAdapter(); - await lbug.loadGraphToLbug(result.graph, result.fileContents); - - if (import.meta.env.DEV) { - const stats = await lbug.getLbugStats(); - console.log('LadybugDB loaded:', stats); - console.log('📁 Stored', storedFileContents.size, 'files for grep/read tools'); - } + await loadResultIntoWorkerState(result); } catch { // LadybugDB is optional - silently continue without it } @@ -357,6 +342,17 @@ const workerApi = { return serializePipelineResult(result); }, + async hydrateServerGraph(serialized: SerializablePipelineResult): Promise { + const graph = createKnowledgeGraph(); + serialized.nodes.forEach((node) => graph.addNode(node)); + serialized.relationships.forEach((relationship) => graph.addRelationship(relationship)); + + await loadResultIntoWorkerState({ + graph, + fileContents: new Map(Object.entries(serialized.fileContents)), + }); + }, + // ============================================================ // Embedding Pipeline Methods // ============================================================ From fa765beec1a55e1b78b0ff0904695aac1ba81a40 Mon Sep 17 00:00:00 2001 From: JayceeB1 <45721154+JayceeB1@users.noreply.github.com> Date: Sat, 21 Mar 2026 23:02:39 +0100 Subject: [PATCH 2/3] test: add server-mode hydration regressions --- gitnexus-web/package-lock.json | 1072 ++++++++++++++++- gitnexus-web/package.json | 6 +- gitnexus-web/src/core/lbug/lbug-adapter.ts | 15 +- .../src/core/lbug/query-result.test.ts | 41 + gitnexus-web/src/core/lbug/query-result.ts | 21 + gitnexus-web/src/workers/ingestion.worker.ts | 10 +- .../workers/server-graph-hydration.test.ts | 58 + .../src/workers/server-graph-hydration.ts | 24 + gitnexus-web/tsconfig.app.json | 3 +- 9 files changed, 1217 insertions(+), 33 deletions(-) create mode 100644 gitnexus-web/src/core/lbug/query-result.test.ts create mode 100644 gitnexus-web/src/core/lbug/query-result.ts create mode 100644 gitnexus-web/src/workers/server-graph-hydration.test.ts create mode 100644 gitnexus-web/src/workers/server-graph-hydration.ts diff --git a/gitnexus-web/package-lock.json b/gitnexus-web/package-lock.json index dd7bce991..6b31e660a 100644 --- a/gitnexus-web/package-lock.json +++ b/gitnexus-web/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@huggingface/transformers": "^3.0.0", "@isomorphic-git/lightning-fs": "^4.6.2", - "@ladybugdb/wasm-core": "^0.15.1", + "@ladybugdb/wasm-core": "^0.15.2", "@langchain/anthropic": "^1.3.10", "@langchain/core": "^1.1.15", "@langchain/google-genai": "^2.1.10", @@ -64,7 +64,8 @@ "tree-sitter-wasms": "^0.1.13", "typescript": "^5.4.5", "vite": "^5.2.0", - "vite-plugin-static-copy": "^3.1.4" + "vite-plugin-static-copy": "^3.1.4", + "vitest": "^4.0.18" } }, "node_modules/@antfu/install-pkg": { @@ -568,6 +569,17 @@ "node": ">=16" } }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -578,6 +590,16 @@ "tslib": "^2.4.0" } }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", @@ -1644,9 +1666,9 @@ } }, "node_modules/@ladybugdb/wasm-core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@ladybugdb/wasm-core/-/wasm-core-0.15.1.tgz", - "integrity": "sha512-dHEq8inJQBkHnJrqZMKGdltSfeSv9OHECkzWQixqDLApXXGlbJ5Ugq5rRfk2PLJuZ74LVHT0cZvcn4JLmsnAIA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@ladybugdb/wasm-core/-/wasm-core-0.15.2.tgz", + "integrity": "sha512-KIR+DBKPMEJlyBxESJF0t/hwQhtlshyhJwj7L5d8nF98R0+J/4bFYZN3mqzagQyi5CfMeIVqRp7gTxfJZ/gEuw==", "license": "MIT", "dependencies": { "threads": "^1.7.0", @@ -1949,6 +1971,22 @@ "langium": "3.3.1" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1987,6 +2025,16 @@ "node": ">= 8" } }, + "node_modules/@oxc-project/types": { + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -2051,6 +2099,279 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.53", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", @@ -2432,6 +2753,13 @@ "sigma": ">=3.0.0-beta.10" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/core": { "version": "1.15.8", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.8.tgz", @@ -2962,6 +3290,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3007,6 +3345,17 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -3269,6 +3618,13 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -3545,6 +3901,92 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/expect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", + "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", + "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", + "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.0", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", + "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", + "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", @@ -3675,6 +4117,16 @@ "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async-listen": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", @@ -3988,6 +4440,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5219,6 +5681,16 @@ "node": ">=0.8.x" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7883,6 +8355,17 @@ "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==", "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/ollama": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.6.3.tgz", @@ -8208,9 +8691,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -8620,6 +9103,47 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", "license": "Unlicense" }, + "node_modules/rolldown": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", @@ -8861,6 +9385,13 @@ "@img/sharp-win32-x64": "0.34.5" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/sigma": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sigma/-/sigma-3.0.2.tgz", @@ -8960,6 +9491,20 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -9097,6 +9642,13 @@ "esm": "^3.2.25" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", @@ -9123,6 +9675,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-buffer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", @@ -10054,6 +10616,483 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/vitest": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", + "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", + "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/vitest/node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/vitest/node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", + "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.10", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -10154,6 +11193,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/gitnexus-web/package.json b/gitnexus-web/package.json index f20e0b35d..6de001d94 100644 --- a/gitnexus-web/package.json +++ b/gitnexus-web/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest run" }, "dependencies": { "@huggingface/transformers": "^3.0.0", @@ -65,6 +66,7 @@ "tree-sitter-wasms": "^0.1.13", "typescript": "^5.4.5", "vite": "^5.2.0", - "vite-plugin-static-copy": "^3.1.4" + "vite-plugin-static-copy": "^3.1.4", + "vitest": "^4.0.18" } } diff --git a/gitnexus-web/src/core/lbug/lbug-adapter.ts b/gitnexus-web/src/core/lbug/lbug-adapter.ts index 9f868580a..a6bbbc8b6 100644 --- a/gitnexus-web/src/core/lbug/lbug-adapter.ts +++ b/gitnexus-web/src/core/lbug/lbug-adapter.ts @@ -16,6 +16,7 @@ import { NodeTableName, } from './schema'; import { generateAllCSVs } from './csv-generator'; +import { getQueryRows } from './query-result'; // Holds the reference to the dynamically loaded module let lbug: any = null; @@ -230,20 +231,6 @@ const escapeTableName = (table: string): string => { return BACKTICK_TABLES.has(table) ? `\`${table}\`` : table; }; -const getQueryRows = async (result: any): Promise => { - if (!result) return []; - if (typeof result.getAllObjects === 'function') { - return await result.getAllObjects(); - } - if (typeof result.getAllRows === 'function') { - return await result.getAllRows(); - } - if (typeof result.getAll === 'function') { - return await result.getAll(); - } - throw new Error('Unsupported LadybugDB QueryResult shape'); -}; - /** Tables with isExported column (TypeScript/JS-native types) */ const TABLES_WITH_EXPORTED = new Set(['Function', 'Class', 'Interface', 'Method', 'CodeElement']); diff --git a/gitnexus-web/src/core/lbug/query-result.test.ts b/gitnexus-web/src/core/lbug/query-result.test.ts new file mode 100644 index 000000000..f9f3be5b8 --- /dev/null +++ b/gitnexus-web/src/core/lbug/query-result.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { getQueryRows } from './query-result'; + +describe('getQueryRows', () => { + it('prefers getAllObjects when available', async () => { + const rows = [{ name: 'foo' }]; + const getAllObjects = vi.fn().mockResolvedValue(rows); + const getAllRows = vi.fn().mockResolvedValue([{ name: 'bar' }]); + const getAll = vi.fn().mockResolvedValue([['baz']]); + + await expect(getQueryRows({ getAllObjects, getAllRows, getAll })).resolves.toEqual(rows); + + expect(getAllObjects).toHaveBeenCalledTimes(1); + expect(getAllRows).not.toHaveBeenCalled(); + expect(getAll).not.toHaveBeenCalled(); + }); + + it('falls back to getAllRows when getAllObjects is absent', async () => { + const rows = [{ name: 'bar' }]; + const getAllRows = vi.fn().mockResolvedValue(rows); + const getAll = vi.fn().mockResolvedValue([['baz']]); + + await expect(getQueryRows({ getAllRows, getAll })).resolves.toEqual(rows); + + expect(getAllRows).toHaveBeenCalledTimes(1); + expect(getAll).not.toHaveBeenCalled(); + }); + + it('falls back to getAll as a final fallback', async () => { + const rows = [['baz']]; + const getAll = vi.fn().mockResolvedValue(rows); + + await expect(getQueryRows({ getAll })).resolves.toEqual(rows); + expect(getAll).toHaveBeenCalledTimes(1); + }); + + it('throws when no supported query API is exposed', async () => { + await expect(getQueryRows({})).rejects.toThrow('Unsupported LadybugDB QueryResult shape'); + }); +}); diff --git a/gitnexus-web/src/core/lbug/query-result.ts b/gitnexus-web/src/core/lbug/query-result.ts new file mode 100644 index 000000000..4496ed5fd --- /dev/null +++ b/gitnexus-web/src/core/lbug/query-result.ts @@ -0,0 +1,21 @@ +export const getQueryRows = async (result: unknown): Promise => { + if (!result || typeof result !== 'object') return []; + + const queryResult = result as { + getAllObjects?: () => Promise; + getAllRows?: () => Promise; + getAll?: () => Promise; + }; + + if (typeof queryResult.getAllObjects === 'function') { + return await queryResult.getAllObjects(); + } + if (typeof queryResult.getAllRows === 'function') { + return await queryResult.getAllRows(); + } + if (typeof queryResult.getAll === 'function') { + return await queryResult.getAll(); + } + + throw new Error('Unsupported LadybugDB QueryResult shape'); +}; diff --git a/gitnexus-web/src/workers/ingestion.worker.ts b/gitnexus-web/src/workers/ingestion.worker.ts index 48ae0b062..cb7a003b0 100644 --- a/gitnexus-web/src/workers/ingestion.worker.ts +++ b/gitnexus-web/src/workers/ingestion.worker.ts @@ -19,6 +19,7 @@ import { enrichClustersBatch, ClusterMemberInfo, ClusterEnrichment } from '../co import { CommunityNode } from '../core/ingestion/community-processor'; import { PipelineResult } from '../types/pipeline'; import { buildCodebaseContext, type CodebaseContext } from '../core/llm/context-builder'; +import { hydrateSerializedServerGraph } from './server-graph-hydration'; import { buildBM25Index, searchBM25, @@ -343,14 +344,7 @@ const workerApi = { }, async hydrateServerGraph(serialized: SerializablePipelineResult): Promise { - const graph = createKnowledgeGraph(); - serialized.nodes.forEach((node) => graph.addNode(node)); - serialized.relationships.forEach((relationship) => graph.addRelationship(relationship)); - - await loadResultIntoWorkerState({ - graph, - fileContents: new Map(Object.entries(serialized.fileContents)), - }); + await hydrateSerializedServerGraph(serialized, loadResultIntoWorkerState); }, // ============================================================ diff --git a/gitnexus-web/src/workers/server-graph-hydration.test.ts b/gitnexus-web/src/workers/server-graph-hydration.test.ts new file mode 100644 index 000000000..ede5f0523 --- /dev/null +++ b/gitnexus-web/src/workers/server-graph-hydration.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it, vi } from 'vitest'; + +import type { SerializablePipelineResult } from '../types/pipeline'; +import { buildPipelineResultFromSerialized, hydrateSerializedServerGraph } from './server-graph-hydration'; + +describe('server graph hydration helpers', () => { + const serialized: SerializablePipelineResult = { + nodes: [ + { + id: 'file:src/foo.ts', + label: 'File' as const, + properties: { + name: 'foo.ts', + filePath: 'src/foo.ts', + content: 'export function foo() {}', + }, + }, + { + id: 'func:src/foo.ts:foo', + label: 'Function' as const, + properties: { + name: 'foo', + filePath: 'src/foo.ts', + }, + }, + ], + relationships: [ + { + source: 'file:src/foo.ts', + target: 'func:src/foo.ts:foo', + type: 'CONTAINS' as const, + properties: { type: 'CONTAINS' }, + }, + ], + fileContents: { + 'src/foo.ts': 'export function foo() {}', + }, + }; + + it('rebuilds a graph and file map from serialized server payloads', () => { + const result = buildPipelineResultFromSerialized(serialized); + + expect(result.graph.nodeCount).toBe(2); + expect(result.graph.relationshipCount).toBe(1); + expect(result.fileContents.get('src/foo.ts')).toBe('export function foo() {}'); + }); + + it('delegates the rebuilt result to the worker-side loader', async () => { + const loadResult = vi.fn().mockResolvedValue(undefined); + + const result = await hydrateSerializedServerGraph(serialized, loadResult); + + expect(loadResult).toHaveBeenCalledTimes(1); + expect(loadResult).toHaveBeenCalledWith(result); + expect(result.graph.nodeCount).toBe(2); + expect(result.fileContents.size).toBe(1); + }); +}); diff --git a/gitnexus-web/src/workers/server-graph-hydration.ts b/gitnexus-web/src/workers/server-graph-hydration.ts new file mode 100644 index 000000000..110315bbc --- /dev/null +++ b/gitnexus-web/src/workers/server-graph-hydration.ts @@ -0,0 +1,24 @@ +import { createKnowledgeGraph } from '../core/graph/graph'; +import type { PipelineResult, SerializablePipelineResult } from '../types/pipeline'; + +export const buildPipelineResultFromSerialized = ( + serialized: SerializablePipelineResult, +): PipelineResult => { + const graph = createKnowledgeGraph(); + serialized.nodes.forEach((node) => graph.addNode(node)); + serialized.relationships.forEach((relationship) => graph.addRelationship(relationship)); + + return { + graph, + fileContents: new Map(Object.entries(serialized.fileContents)), + }; +}; + +export const hydrateSerializedServerGraph = async ( + serialized: SerializablePipelineResult, + loadResult: (result: PipelineResult) => Promise, +): Promise => { + const result = buildPipelineResultFromSerialized(serialized); + await loadResult(result); + return result; +}; diff --git a/gitnexus-web/tsconfig.app.json b/gitnexus-web/tsconfig.app.json index f429ad3ea..b9ea9c2f7 100644 --- a/gitnexus-web/tsconfig.app.json +++ b/gitnexus-web/tsconfig.app.json @@ -20,5 +20,6 @@ }, "types": ["vite/client"] }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"] } From c87cb818929241fc4a6e6d202729a6682fd4c0bd Mon Sep 17 00:00:00 2001 From: JayceeB1 <45721154+JayceeB1@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:54:37 +0100 Subject: [PATCH 3/3] fix: await server connect hydration flow --- gitnexus-web/src/App.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gitnexus-web/src/App.tsx b/gitnexus-web/src/App.tsx index 64cb5fe39..3233ecd01 100644 --- a/gitnexus-web/src/App.tsx +++ b/gitnexus-web/src/App.tsx @@ -157,11 +157,14 @@ const AppContent = () => { } setFileContents(fileMap); - await hydrateServerGraph(result); + try { + await hydrateServerGraph(result); - // Transition directly to exploring view - setViewMode('exploring'); - setProgress(null); + // Transition directly to exploring view + setViewMode('exploring'); + } finally { + setProgress(null); + } // Hydrate the worker-side DB (LadybugDB + BM25) so Query/Processes/embeddings work hydrateWorkerFromServer(result.nodes, result.relationships, result.fileContents).then(() => { @@ -260,7 +263,7 @@ const AppContent = () => { onFileSelect={handleFileSelect} onGitClone={handleGitClone} onServerConnect={async (result, serverUrl) => { - handleServerConnect(result); + await handleServerConnect(result); if (serverUrl) { const baseUrl = normalizeServerUrl(serverUrl); setServerBaseUrl(baseUrl);