@zipbul/gildash
v0.7.0
Published
TypeScript code indexing and dependency graph engine for Bun
Maintainers
Readme
@zipbul/gildash
English | 한국어
A Bun-native TypeScript code intelligence engine.
gildash indexes your TypeScript codebase into a local SQLite database, then lets you search symbols, trace cross-file relationships, analyze dependency graphs, and match structural patterns — all with incremental, file-watcher-driven updates.
💡 Why gildash?
| Problem | How gildash solves it |
|---------|----------------------|
| "Which files break if I change this module?" | Directed import graph with transitive impact analysis |
| "Are there any circular dependencies?" | Cycle detection across the full import graph |
| "Where is this symbol actually defined?" | Re-export chain resolution to the original source |
| "Find every console.log(...) call" | AST-level structural pattern search via ast-grep |
✨ Features
- Symbol extraction — Functions, classes, variables, types, interfaces, enums, and properties extracted via oxc-parser AST
- Relation tracking —
import,re-exports,type-references,calls,extends,implementsrelationships across files - Full-text search — SQLite FTS5-powered symbol name search with exact match, regex, and decorator filtering
- Dependency graph — Directed import graph with cycle detection, transitive impact analysis, and internal caching
- Structural pattern matching — AST-level code search via @ast-grep/napi
- Incremental indexing —
@parcel/watcher-based file change detection; only re-indexes modified files - Symbol-level diff —
changedSymbolsinIndexResulttracks added/modified/removed symbols per index cycle - Multi-process safe — Owner/reader role separation guarantees a single writer per database
- Scan-only mode —
watchMode: falsefor one-shot indexing without file watcher overhead - tsconfig.json JSONC — Path alias resolution parses comments and trailing commas in
tsconfig.json - Semantic layer (opt-in) — tsc TypeChecker integration for resolved types, references, implementations, and module interface analysis
📋 Requirements
- Bun v1.3 or higher
- TypeScript source files:
.ts,.mts,.cts
📦 Installation
bun add @zipbul/gildashOptional peer —
typescript(>=5.0.0) is needed only when usingsemantic: true.
🚀 Quick Start
import { Gildash } from '@zipbul/gildash';
// 1. Open — indexes every .ts file on first run, then watches for changes
const ledger = await Gildash.open({
projectRoot: '/absolute/path/to/project',
});
// 2. Search — find symbols by name
const symbols = ledger.searchSymbols({ text: 'UserService', kind: 'class' });
symbols.forEach(s => console.log(`${s.name} → ${s.filePath}`));
// 3. Close — release resources
await ledger.close();That's it. Project discovery (monorepo-aware), incremental re-indexing, and multi-process safety are handled automatically.
📖 Usage Guide
Symbol Search
Search indexed symbols with FTS5 full-text, exact match, regex, or decorator filters.
// Full-text search (FTS5 prefix matching)
const hits = ledger.searchSymbols({ text: 'handle' });
// Exact name match
const exact = ledger.searchSymbols({ text: 'UserService', exact: true });
// Regex pattern
const handlers = ledger.searchSymbols({ regex: '^handle.*Click$' });
// Decorator filter
const injectables = ledger.searchSymbols({ decorator: 'Injectable' });
// Combine filters
const exportedClasses = ledger.searchSymbols({
kind: 'class',
isExported: true,
limit: 50,
});Use searchRelations() to find cross-file relationships:
const imports = ledger.searchRelations({ srcFilePath: 'src/app.ts', type: 'imports' });
const callers = ledger.searchRelations({ dstSymbolName: 'processOrder', type: 'calls' });For monorepo projects, searchAllSymbols() and searchAllRelations() search across every discovered project.
Dependency Analysis
Analyze import graphs, detect cycles, and compute change impact.
// Direct imports / importers
const deps = ledger.getDependencies('src/app.ts');
const importers = ledger.getDependents('src/utils.ts');
// Transitive impact — which files are affected by a change?
const affected = await ledger.getAffected(['src/utils.ts']);
// Full import graph (adjacency list)
const graph = await ledger.getImportGraph();
// Transitive dependencies (forward BFS)
const transitive = await ledger.getTransitiveDependencies('src/app.ts');
// Circular dependency detection
const hasCycles = await ledger.hasCycle();
const cyclePaths = await ledger.getCyclePaths(); // all elementary circuits
const limited = await ledger.getCyclePaths(undefined, { maxCycles: 100 }); // undefined = use default projectCode Quality Analysis
Inspect module interfaces and measure coupling.
// File statistics — line count, symbol count, size
const stats = ledger.getFileStats('src/app.ts');
// Fan-in / fan-out coupling metrics
const fan = await ledger.getFanMetrics('src/app.ts');
// Module public interface — all exported symbols with metadata
const iface = ledger.getModuleInterface('src/services/user.ts');
// Full symbol detail — members, jsDoc, decorators, type info
const full = ledger.getFullSymbol('UserService', 'src/services/user.ts');Pattern Matching & Tracing
Search code by AST structure and trace symbol origins through re-export chains.
// Structural pattern search (ast-grep syntax)
const logs = await ledger.findPattern('console.log($$$)');
const hooks = await ledger.findPattern('useState($A)', {
filePaths: ['src/components/App.tsx'],
});
// Resolve re-export chains — find where a symbol is actually defined
const resolved = ledger.resolveSymbol('MyComponent', 'src/index.ts');
// Heritage chain — extends/implements tree traversal
const tree = await ledger.getHeritageChain('UserService', 'src/services/user.ts');🔧 Scan-only Mode
For CI pipelines or one-shot analysis, disable the file watcher:
const ledger = await Gildash.open({
projectRoot: '/path/to/project',
watchMode: false, // no watcher, no heartbeat
});
// ... run your queries ...
await ledger.close({ cleanup: true }); // delete DB files after use❌ Error Handling
Public methods return values directly and throw GildashError on failure. Use instanceof to branch:
import { Gildash, GildashError } from '@zipbul/gildash';
try {
const symbols = ledger.searchSymbols({ text: 'foo' });
console.log(`Found ${symbols.length} symbols`);
} catch (e) {
if (e instanceof GildashError) {
console.error(`[${e.type}] ${e.message}`);
}
}⚙️ Configuration
Gildash.open(options)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| projectRoot | string | — | Absolute path to project root (required) |
| extensions | string[] | ['.ts', '.mts', '.cts'] | File extensions to index |
| ignorePatterns | string[] | [] | Glob patterns to exclude |
| parseCacheCapacity | number | 500 | LRU parse-cache capacity |
| logger | Logger | console | Custom logger ({ error(...args): void }) |
| watchMode | boolean | true | false disables the file watcher (scan-only mode) |
| semantic | boolean | false | Enable tsc TypeChecker-backed semantic analysis |
Returns Promise<Gildash>. Throws GildashError on failure.
Note:
semantic: truerequires atsconfig.jsonin the project root. If not found,Gildash.open()throws aGildashError.
🔍 API Reference
Search
| Method | Returns | Description |
|--------|---------|-------------|
| searchSymbols(query) | SymbolSearchResult[] | FTS5 full-text + exact / regex / decorator filters |
| searchRelations(query) | StoredCodeRelation[] | Filter by file, symbol, or relation type |
| searchAllSymbols(query) | SymbolSearchResult[] | Cross-project symbol search |
| searchAllRelations(query) | StoredCodeRelation[] | Cross-project relation search |
| listIndexedFiles(project?) | FileRecord[] | All indexed files for a project |
| getSymbolsByFile(filePath) | SymbolSearchResult[] | All symbols in a single file |
Dependency Graph
| Method | Returns | Description |
|--------|---------|-------------|
| getDependencies(filePath) | string[] | Files imported by filePath |
| getDependents(filePath) | string[] | Files that import filePath |
| getAffected(changedFiles) | Promise<string[]> | Transitive impact set |
| hasCycle(project?) | Promise<boolean> | Circular dependency check |
| getCyclePaths(project?, opts?) | Promise<string[][]> | All cycle paths (Tarjan SCC + Johnson's). opts.maxCycles limits results. |
| getImportGraph(project?) | Promise<Map> | Full adjacency list |
| getTransitiveDependencies(filePath) | Promise<string[]> | Forward transitive BFS |
Analysis
| Method | Returns | Description |
|--------|---------|-------------|
| getFullSymbol(name, filePath) | FullSymbol \| null | Members, jsDoc, decorators, type info |
| getFileStats(filePath) | FileStats | Line count, symbol count, size |
| getFanMetrics(filePath) | Promise<FanMetrics> | Fan-in / fan-out coupling |
| getModuleInterface(filePath) | ModuleInterface | Public exports with metadata |
| getInternalRelations(filePath) | StoredCodeRelation[] | Intra-file relations |
| diffSymbols(before, after) | SymbolDiff | Snapshot diff (added / removed / modified) |
Semantic (opt-in)
Requires semantic: true at open time.
| Method | Returns | Description |
|--------|---------|-------------|
| getResolvedType(name, filePath) | ResolvedType \| null | Resolved type via tsc TypeChecker |
| getSemanticReferences(name, filePath) | SemanticReference[] | All references to a symbol |
| getImplementations(name, filePath) | Implementation[] | Interface / abstract class implementations |
| getSemanticModuleInterface(filePath) | SemanticModuleInterface | Module exports with resolved types |
getFullSymbol() automatically enriches the result with a resolvedType field when semantic is enabled.
searchSymbols({ resolvedType }) filters symbols by their resolved type string.
Advanced
| Method | Returns | Description |
|--------|---------|-------------|
| findPattern(pattern, opts?) | Promise<PatternMatch[]> | AST structural search (ast-grep) |
| resolveSymbol(name, filePath) | ResolvedSymbol | Follow re-export chain to original |
| getHeritageChain(name, filePath) | Promise<HeritageNode> | extends / implements tree |
| batchParse(filePaths, opts?) | Promise<Map> | Concurrent multi-file parsing. opts: oxc-parser ParserOptions. |
Lifecycle & Low-level
| Method | Returns | Description |
|--------|---------|-------------|
| reindex() | Promise<IndexResult> | Force full re-index (owner only) |
| onIndexed(callback) | () => void | Subscribe to index-complete events |
| parseSource(filePath, src, opts?) | ParsedFile | Parse & cache a single file. opts: oxc-parser ParserOptions. |
| extractSymbols(parsed) | ExtractedSymbol[] | Extract symbols from parsed AST |
| extractRelations(parsed) | CodeRelation[] | Extract relations from parsed AST |
| getParsedAst(filePath) | ParsedFile \| undefined | Cached AST lookup (read-only) |
| getFileInfo(filePath) | FileRecord \| null | File metadata (hash, mtime, size) |
| getStats(project?) | SymbolStats | Symbol / file count statistics |
| projects | ProjectBoundary[] | Discovered project boundaries |
| close(opts?) | Promise<void> | Shutdown (pass { cleanup: true } to delete DB) |
// ── Search ──────────────────────────────────────────────────────────────
interface SymbolSearchQuery {
text?: string; // FTS5 full-text query
exact?: boolean; // exact name match (not prefix)
kind?: SymbolKind; // 'function' | 'method' | 'class' | 'variable' | 'type' | 'interface' | 'enum' | 'property'
filePath?: string;
isExported?: boolean;
project?: string;
limit?: number; // default: 100
decorator?: string; // e.g. 'Injectable'
regex?: string; // regex applied to symbol name
}
interface SymbolSearchResult {
id: number;
filePath: string;
kind: SymbolKind;
name: string;
span: { start: { line: number; column: number }; end: { line: number; column: number } };
isExported: boolean;
signature: string | null;
fingerprint: string | null;
detail: Record<string, unknown>;
}
interface RelationSearchQuery {
srcFilePath?: string;
srcSymbolName?: string;
dstFilePath?: string;
dstSymbolName?: string;
dstProject?: string; // filter by destination project
type?: 'imports' | 'type-references' | 're-exports' | 'calls' | 'extends' | 'implements';
project?: string;
limit?: number; // default: 500
}
interface CodeRelation {
type: 'imports' | 'type-references' | 're-exports' | 'calls' | 'extends' | 'implements';
srcFilePath: string;
srcSymbolName: string | null;
dstFilePath: string;
dstSymbolName: string | null;
metaJson?: string;
meta?: Record<string, unknown>; // auto-parsed from metaJson
}
/** CodeRelation enriched with the destination project identifier. */
interface StoredCodeRelation extends CodeRelation {
dstProject: string;
}
// ── Analysis ────────────────────────────────────────────────────────────
interface FullSymbol extends SymbolSearchResult {
members?: Array<{
name: string;
kind: string;
type?: string;
visibility?: string;
isStatic?: boolean;
isReadonly?: boolean;
}>;
jsDoc?: string;
parameters?: string;
returnType?: string;
heritage?: string[];
decorators?: Array<{ name: string; arguments?: string }>;
typeParameters?: string;
}
interface FileStats {
filePath: string;
lineCount: number;
symbolCount: number;
relationCount: number;
size: number;
exportedSymbolCount: number;
}
interface FanMetrics {
filePath: string;
fanIn: number; // files importing this file
fanOut: number; // files this file imports
}
interface ModuleInterface {
filePath: string;
exports: Array<{
name: string;
kind: SymbolKind;
parameters?: string;
returnType?: string;
jsDoc?: string;
}>;
}
interface SymbolDiff {
added: SymbolSearchResult[];
removed: SymbolSearchResult[];
modified: Array<{ before: SymbolSearchResult; after: SymbolSearchResult }>;
}
// ── Advanced ────────────────────────────────────────────────────────────
interface PatternMatch {
filePath: string;
startLine: number;
endLine: number;
matchedText: string;
}
interface ResolvedSymbol {
originalName: string;
originalFilePath: string;
reExportChain: Array<{ filePath: string; exportedAs: string }>;
circular: boolean; // true when re-export chain contains a cycle
}
interface HeritageNode {
symbolName: string;
filePath: string;
kind?: 'extends' | 'implements';
children: HeritageNode[];
}
// ── Indexing ────────────────────────────────────────────────────────────
interface IndexResult {
indexedFiles: number;
removedFiles: number;
totalSymbols: number;
totalRelations: number;
durationMs: number;
changedFiles: string[];
deletedFiles: string[];
failedFiles: string[];
changedSymbols: {
added: Array<{ name: string; filePath: string; kind: string }>;
modified: Array<{ name: string; filePath: string; kind: string }>;
removed: Array<{ name: string; filePath: string; kind: string }>;
};
}
interface FileRecord {
project: string;
filePath: string;
mtimeMs: number;
size: number;
contentHash: string;
updatedAt: string;
lineCount?: number | null;
}
// ── Errors ──────────────────────────────────────────────────────────────
class GildashError extends Error {
readonly type: GildashErrorType; // see Error Types table below
readonly message: string;
readonly cause?: unknown; // inherited from Error
}⚠️ Error Types
| Type | When |
|------|------|
| watcher | File watcher start / stop failure |
| parse | AST parsing failure |
| extract | Symbol / relation extraction failure |
| index | Indexing pipeline failure |
| store | Database operation failure |
| search | Search query failure |
| closed | Operation on a closed instance |
| semantic | Semantic layer not enabled or tsc error |
| validation | Invalid input (e.g. missing node_modules package) |
| close | Error during shutdown |
🏗 Architecture
Gildash (Facade)
├── Parser — oxc-parser-based TypeScript AST parsing
├── Extractor — Symbol & relation extraction (imports, re-exports, type-refs, calls, heritage)
├── Store — bun:sqlite + drizzle-orm (files · symbols · relations · FTS5) at `.gildash/gildash.db`
├── Indexer — File change → parse → extract → store pipeline, symbol-level diff
├── Search — FTS + regex + decorator search, relation queries, dependency graph, ast-grep
├── Semantic — tsc TypeChecker integration (opt-in): types, references, implementations
└── Watcher — @parcel/watcher + owner/reader role managementOwner / Reader Pattern
When multiple processes share the same SQLite database, gildash enforces a single-writer guarantee:
- Owner — Runs the file watcher, performs indexing, sends a heartbeat every 30 s
- Reader — Read-only access; polls owner health every 60 s and self-promotes if the owner goes stale
⬆️ Upgrading
From 0.5.x to 0.6.0
Breaking: @zipbul/result is no longer part of the public API. All methods now return values directly and throw GildashError on failure.
- import { isErr } from '@zipbul/result';
- const result = ledger.searchSymbols({ text: 'foo' });
- if (isErr(result)) { console.error(result.data.message); }
- else { console.log(result); }
+ const symbols = ledger.searchSymbols({ text: 'foo' }); // throws GildashError- Remove
@zipbul/resultfrom your dependencies (no longer a peer dependency) - Replace
isErr()checks withtry/catchusinginstanceof GildashError getFullSymbol(),getFileInfo(),getResolvedType()returnnullinstead of an error when not foundresolveSymbol()returns{ circular: true }for circular re-exports instead of throwing
From 0.4.x to 0.5.0
The database directory was renamed from .zipbul/ to .gildash/. The database is now stored at <projectRoot>/.gildash/gildash.db.
Existing .zipbul/ data is not migrated automatically. On first run, a fresh database is created at .gildash/gildash.db. Delete .zipbul/ manually after upgrading.
