Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"@commitlint/config-conventional": "^20.0",
"@huggingface/transformers": "^3.8.1",
"@tree-sitter-grammars/tree-sitter-hcl": "^1.2.0",
"@types/better-sqlite3": "^7.6.13",
"@vitest/coverage-v8": "^4.0.18",
"commit-and-tag-version": "^12.5",
"husky": "^9.1",
Expand Down
1 change: 0 additions & 1 deletion src/cli/commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export const command: CommandDefinition = {
const dbPath = findDbPath();
const fs = await import('node:fs');
if (fs.existsSync(dbPath)) {
// @ts-expect-error -- better-sqlite3 default export typing
const db = new Database(dbPath, { readonly: true });
const buildEngine = getBuildMeta(db, 'engine');
const buildVersion = getBuildMeta(db, 'codegraph_version');
Expand Down
4 changes: 2 additions & 2 deletions src/cli/shared/open-graph.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type Database from 'better-sqlite3';
import { openReadonlyOrFail } from '../../db/index.js';
import type { BetterSqlite3Database } from '../../types.js';

/**
* Open the graph database in readonly mode with a clean close() handle.
*/
export function openGraph(opts: { db?: string } = {}): {
db: Database.Database;
db: BetterSqlite3Database;
close: () => void;
} {
const db = openReadonlyOrFail(opts.db);
Expand Down
15 changes: 2 additions & 13 deletions src/db/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,7 @@ export function openDb(dbPath: string): LockedDatabase {
const dir = path.dirname(dbPath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
acquireAdvisoryLock(dbPath);
// vendor.d.ts declares Database as a callable; cast through unknown for construct usage
const db = new (
Database as unknown as new (
path: string,
opts?: Record<string, unknown>,
) => LockedDatabase
)(dbPath);
const db = new Database(dbPath) as unknown as LockedDatabase;
db.pragma('journal_mode = WAL');
db.pragma('busy_timeout = 5000');
db.__lockPath = `${dbPath}.lock`;
Expand Down Expand Up @@ -214,12 +208,7 @@ export function openReadonlyOrFail(customPath?: string): BetterSqlite3Database {
{ file: dbPath },
);
}
const db = new (
Database as unknown as new (
path: string,
opts?: Record<string, unknown>,
) => BetterSqlite3Database
)(dbPath, { readonly: true });
const db = new Database(dbPath, { readonly: true }) as unknown as BetterSqlite3Database;

// Warn once per process if the DB was built with a different codegraph version
if (!_versionWarned) {
Expand Down
4 changes: 2 additions & 2 deletions src/domain/graph/builder/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* Each stage reads what it needs and writes what it produces.
* This replaces the closure-captured locals in the old monolithic buildGraph().
*/
import type BetterSqlite3 from 'better-sqlite3';
import type {
BetterSqlite3Database,
BuildGraphOpts,
CodegraphConfig,
EngineOpts,
Expand All @@ -20,7 +20,7 @@ import type {
export class PipelineContext {
// ── Inputs (set during setup) ──────────────────────────────────────
rootDir!: string;
db!: BetterSqlite3.Database;
db!: BetterSqlite3Database;
dbPath!: string;
config!: CodegraphConfig;
opts!: BuildGraphOpts;
Expand Down
26 changes: 15 additions & 11 deletions src/domain/graph/builder/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import type BetterSqlite3 from 'better-sqlite3';
import { purgeFilesData } from '../../../db/index.js';
import { debug, warn } from '../../../infrastructure/logger.js';
import { warn } from '../../../infrastructure/logger.js';
import { EXTENSIONS, IGNORE_DIRS } from '../../../shared/constants.js';
import type { BetterSqlite3Database, CodegraphConfig, PathAliases } from '../../../types.js';
import type {
BetterSqlite3Database,
CodegraphConfig,
PathAliases,
SqliteStatement,
} from '../../../types.js';

export const BUILTIN_RECEIVERS: Set<string> = new Set([
'console',
Expand Down Expand Up @@ -148,7 +152,7 @@ export function loadPathAliases(rootDir: string): PathAliases {
}
break;
} catch (err: unknown) {
debug(`Failed to parse ${configName}: ${(err as Error).message}`);
warn(`Failed to parse ${configName}: ${(err as Error).message}`);
}
}
return aliases;
Expand Down Expand Up @@ -198,7 +202,7 @@ export function readFileSafe(filePath: string, retries: number = 2): string {
* Purge all graph data for the specified files.
*/
export function purgeFilesFromGraph(
db: BetterSqlite3.Database,
db: BetterSqlite3Database,
files: string[],
options: Record<string, unknown> = {},
): void {
Expand All @@ -210,10 +214,10 @@ export function purgeFilesFromGraph(
const BATCH_CHUNK = 500;

// Statement caches keyed by chunk size — avoids recompiling for every batch.
const nodeStmtCache = new WeakMap<BetterSqlite3.Database, Map<number, BetterSqlite3.Statement>>();
const edgeStmtCache = new WeakMap<BetterSqlite3.Database, Map<number, BetterSqlite3.Statement>>();
const nodeStmtCache = new WeakMap<BetterSqlite3Database, Map<number, SqliteStatement>>();
const edgeStmtCache = new WeakMap<BetterSqlite3Database, Map<number, SqliteStatement>>();

function getNodeStmt(db: BetterSqlite3.Database, chunkSize: number): BetterSqlite3.Statement {
function getNodeStmt(db: BetterSqlite3Database, chunkSize: number): SqliteStatement {
let cache = nodeStmtCache.get(db);
if (!cache) {
cache = new Map();
Expand All @@ -231,7 +235,7 @@ function getNodeStmt(db: BetterSqlite3.Database, chunkSize: number): BetterSqlit
return stmt;
}

function getEdgeStmt(db: BetterSqlite3.Database, chunkSize: number): BetterSqlite3.Statement {
function getEdgeStmt(db: BetterSqlite3Database, chunkSize: number): SqliteStatement {
let cache = edgeStmtCache.get(db);
if (!cache) {
cache = new Map();
Expand All @@ -253,7 +257,7 @@ function getEdgeStmt(db: BetterSqlite3.Database, chunkSize: number): BetterSqlit
* Batch-insert node rows via multi-value INSERT statements.
* Each row: [name, kind, file, line, end_line, parent_id, qualified_name, scope, visibility]
*/
export function batchInsertNodes(db: BetterSqlite3.Database, rows: unknown[][]): void {
export function batchInsertNodes(db: BetterSqlite3Database, rows: unknown[][]): void {
if (!rows.length) return;
for (let i = 0; i < rows.length; i += BATCH_CHUNK) {
const end = Math.min(i + BATCH_CHUNK, rows.length);
Expand All @@ -272,7 +276,7 @@ export function batchInsertNodes(db: BetterSqlite3.Database, rows: unknown[][]):
* Batch-insert edge rows via multi-value INSERT statements.
* Each row: [source_id, target_id, kind, confidence, dynamic]
*/
export function batchInsertEdges(db: BetterSqlite3.Database, rows: unknown[][]): void {
export function batchInsertEdges(db: BetterSqlite3Database, rows: unknown[][]): void {
if (!rows.length) return;
for (let i = 0; i < rows.length; i += BATCH_CHUNK) {
const end = Math.min(i + BATCH_CHUNK, rows.length);
Expand Down
61 changes: 33 additions & 28 deletions src/domain/graph/builder/incremental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
*/
import fs from 'node:fs';
import path from 'node:path';
import type BetterSqlite3 from 'better-sqlite3';
import { bulkNodeIdsByFile } from '../../../db/index.js';
import { warn } from '../../../infrastructure/logger.js';
import { normalizePath } from '../../../shared/constants.js';
import type { EngineOpts, ExtractorOutput, PathAliases } from '../../../types.js';
import type {
BetterSqlite3Database,
EngineOpts,
ExtractorOutput,
PathAliases,
SqliteStatement,
} from '../../../types.js';
import { parseFileIncremental } from '../../parser.js';
import { computeConfidence, resolveImportPath } from '../resolve.js';
import { BUILTIN_RECEIVERS, readFileSafe } from './helpers.js';
Expand Down Expand Up @@ -64,7 +69,7 @@ function insertFileNodes(stmts: IncrementalStmts, relPath: string, symbols: Extr
// ── Containment edges ──────────────────────────────────────────────────

function buildContainmentEdges(
db: BetterSqlite3.Database,
db: BetterSqlite3Database,
stmts: IncrementalStmts,
relPath: string,
symbols: ExtractorOutput,
Expand Down Expand Up @@ -101,13 +106,13 @@ function buildContainmentEdges(
// ── Reverse-dep cascade ────────────────────────────────────────────────

// Lazily-cached prepared statements for reverse-dep operations
let _revDepDb: BetterSqlite3.Database | null = null;
let _findRevDepsStmt: BetterSqlite3.Statement | null = null;
let _deleteOutEdgesStmt: BetterSqlite3.Statement | null = null;
let _revDepDb: BetterSqlite3Database | null = null;
let _findRevDepsStmt: SqliteStatement | null = null;
let _deleteOutEdgesStmt: SqliteStatement | null = null;

function getRevDepStmts(db: BetterSqlite3.Database): {
findRevDepsStmt: BetterSqlite3.Statement;
deleteOutEdgesStmt: BetterSqlite3.Statement;
function getRevDepStmts(db: BetterSqlite3Database): {
findRevDepsStmt: SqliteStatement;
deleteOutEdgesStmt: SqliteStatement;
} {
if (_revDepDb !== db) {
_revDepDb = db;
Expand All @@ -127,12 +132,12 @@ function getRevDepStmts(db: BetterSqlite3.Database): {
};
}

function findReverseDeps(db: BetterSqlite3.Database, relPath: string): string[] {
function findReverseDeps(db: BetterSqlite3Database, relPath: string): string[] {
const { findRevDepsStmt } = getRevDepStmts(db);
return (findRevDepsStmt.all(relPath, relPath) as Array<{ file: string }>).map((r) => r.file);
}

function deleteOutgoingEdges(db: BetterSqlite3.Database, relPath: string): void {
function deleteOutgoingEdges(db: BetterSqlite3Database, relPath: string): void {
const { deleteOutEdgesStmt } = getRevDepStmts(db);
deleteOutEdgesStmt.run(relPath);
}
Expand All @@ -157,7 +162,7 @@ async function parseReverseDep(
}

function rebuildReverseDepEdges(
db: BetterSqlite3.Database,
db: BetterSqlite3Database,
rootDir: string,
depRelPath: string,
symbols: ExtractorOutput,
Expand Down Expand Up @@ -187,7 +192,7 @@ function rebuildReverseDepEdges(
// ── Directory containment edges ────────────────────────────────────────

function rebuildDirContainment(
_db: BetterSqlite3.Database,
_db: BetterSqlite3Database,
stmts: IncrementalStmts,
relPath: string,
): number {
Expand All @@ -204,7 +209,7 @@ function rebuildDirContainment(

// ── Ancillary table cleanup ────────────────────────────────────────────

function purgeAncillaryData(db: BetterSqlite3.Database, relPath: string): void {
function purgeAncillaryData(db: BetterSqlite3Database, relPath: string): void {
const tryExec = (sql: string, ...args: string[]): void => {
try {
db.prepare(sql).run(...args);
Expand Down Expand Up @@ -239,15 +244,15 @@ function purgeAncillaryData(db: BetterSqlite3.Database, relPath: string): void {
// ── Import edge building ────────────────────────────────────────────────

// Lazily-cached prepared statements for barrel resolution (avoid re-preparing in hot loops)
let _barrelDb: BetterSqlite3.Database | null = null;
let _isBarrelStmt: BetterSqlite3.Statement | null = null;
let _reexportTargetsStmt: BetterSqlite3.Statement | null = null;
let _hasDefStmt: BetterSqlite3.Statement | null = null;

function getBarrelStmts(db: BetterSqlite3.Database): {
isBarrelStmt: BetterSqlite3.Statement;
reexportTargetsStmt: BetterSqlite3.Statement;
hasDefStmt: BetterSqlite3.Statement;
let _barrelDb: BetterSqlite3Database | null = null;
let _isBarrelStmt: SqliteStatement | null = null;
let _reexportTargetsStmt: SqliteStatement | null = null;
let _hasDefStmt: SqliteStatement | null = null;

function getBarrelStmts(db: BetterSqlite3Database): {
isBarrelStmt: SqliteStatement;
reexportTargetsStmt: SqliteStatement;
hasDefStmt: SqliteStatement;
} {
if (_barrelDb !== db) {
_barrelDb = db;
Expand All @@ -273,14 +278,14 @@ function getBarrelStmts(db: BetterSqlite3.Database): {
};
}

function isBarrelFile(db: BetterSqlite3.Database, relPath: string): boolean {
function isBarrelFile(db: BetterSqlite3Database, relPath: string): boolean {
const { isBarrelStmt } = getBarrelStmts(db);
const reexportCount = (isBarrelStmt.get(relPath) as { c: number } | undefined)?.c;
return (reexportCount || 0) > 0;
}

function resolveBarrelTarget(
db: BetterSqlite3.Database,
db: BetterSqlite3Database,
barrelPath: string,
symbolName: string,
visited: Set<string> = new Set(),
Expand Down Expand Up @@ -312,7 +317,7 @@ function resolveBarrelTarget(
* Shared by buildImportEdges (primary file) and Pass 2 of the reverse-dep cascade.
*/
function resolveBarrelImportEdges(
db: BetterSqlite3.Database,
db: BetterSqlite3Database,
stmts: IncrementalStmts,
fileNodeId: number,
resolvedPath: string,
Expand Down Expand Up @@ -344,7 +349,7 @@ function buildImportEdges(
rootDir: string,
fileNodeId: number,
aliases: PathAliases,
db: BetterSqlite3.Database | null,
db: BetterSqlite3Database | null,
): number {
let edgesAdded = 0;
for (const imp of symbols.imports) {
Expand Down Expand Up @@ -504,7 +509,7 @@ function buildCallEdges(
* Parse a single file and update the database incrementally.
*/
export async function rebuildFile(
db: BetterSqlite3.Database,
db: BetterSqlite3Database,
rootDir: string,
filePath: string,
stmts: IncrementalStmts,
Expand Down
4 changes: 2 additions & 2 deletions src/domain/graph/builder/stages/build-edges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
*/
import path from 'node:path';
import { performance } from 'node:perf_hooks';
import type BetterSqlite3 from 'better-sqlite3';
import { getNodeId } from '../../../../db/index.js';
import { loadNative } from '../../../../infrastructure/native.js';
import type {
BetterSqlite3Database,
Call,
ClassRelation,
ExtractorOutput,
Expand Down Expand Up @@ -68,7 +68,7 @@ interface NormalizedTypeEntry {

// ── Node lookup setup ───────────────────────────────────────────────────

function makeGetNodeIdStmt(db: BetterSqlite3.Database): NodeIdStmt {
function makeGetNodeIdStmt(db: BetterSqlite3Database): NodeIdStmt {
return {
get: (name: string, kind: string, file: string, line: number) => {
const id = getNodeId(db, name, kind, file, line);
Expand Down
Loading
Loading