import type { Embedder } from '../embedders/embedder.js';
import type { Chunk, HealthCheckResult, SearchOptions, SearchResult, SourceContext } from '../types.js';
/**
* Escape a string for use in a LanceDB SQL WHERE clause (single-quoted literal).
*
* LanceDB uses DataFusion under the hood, which follows the SQL standard:
* single quotes are escaped by doubling ('' → ') and backslash is treated
* as a literal character (NOT an escape character). Therefore only quote
* doubling is required — escaping backslashes would break path matching.
*/
export declare function escapeSqlString(input: string): string;
export declare class LanceStore {
private db;
private table;
private dbPath;
private embedder;
private hasFtsIndex;
private onWarn;
/**
* Instrumentation: number of times the read path has reopened the handle
* via `refreshReadHandle()`. Exposed via `readRefreshCount` so tests
* (mmnto/totem#1418) can assert the reopen fires on every search.
*/
private readRefreshes;
/**
* Source repo context injected at construction time. Primary stores use
* `{ absolutePathRoot: projectRoot }` with no `sourceRepo`; linked stores
* (mmnto/totem#1294 Cross-Repo Context Mesh) use
* `{ sourceRepo: '', absolutePathRoot: '' }`.
* Passed down to every `search` / `searchFts` call so every SearchResult
* gets stamped with the owning store's identity via `rowToSearchResult`.
*
* **Required** since mmnto/totem#1295 — CR flagged that an optional
* context with a silent `filePath` fallback for `absoluteFilePath` sent
* legacy callers down the wrong repo root instead of failing fast.
* Making the parameter required closes that class of bug at the type
* level and forces every call site to state its source explicitly.
*/
private sourceContext;
constructor(dbPath: string, embedder: Embedder, sourceContext: SourceContext, onWarn?: (msg: string) => void);
/**
* Connect to LanceDB. Auto-heals on version mismatch, corruption,
* or embedder dimension change by deleting the index and signaling
* that a full rebuild is needed.
*/
connect(): Promise;
/**
* Connect for FTS-only use — skips embedder dimension validation.
* Use when the embedder is unavailable (offline, no API key) and
* only FTS search will be performed.
*/
connectFtsOnly(): Promise;
/** Check if the stored vector dimensions differ from the current embedder. */
private hasDimensionMismatch;
/** Detect errors that warrant auto-healing (nuke + rebuild). */
private isHealableError;
/** Delete the entire .lancedb/ directory and reset state. */
private nukeAndReset;
/** Insert chunks into the store. Embeds them first. */
insert(chunks: Chunk[]): Promise;
/**
* Create (or rebuild) the FTS index on the `content` column.
* Must be called after table creation or after incremental adds,
* because LanceDB FTS indexes do not auto-update on `table.add()`.
* Uses `replace: true` (the LanceDB default) to overwrite any stale index.
*/
createFtsIndex(): Promise;
/** Check whether an FTS index exists on the table. */
private detectFtsIndex;
/** Whether the FTS index is available for hybrid search. */
get ftsIndexReady(): boolean;
/**
* Search with optional hybrid mode (vector + FTS with RRF reranking).
* Falls back to vector-only if no FTS index exists.
*
* Opens a fresh LanceDB snapshot for this call (mmnto/totem#1418) so an
* external `totem sync` cannot leave this store reading a stale view,
* and so concurrent searches never invalidate each other's handle.
*/
search(options: SearchOptions): Promise;
/**
* FTS-only search. No embedder required.
* Use when embedding is unavailable (offline, no API key, cold-start fallback).
* Requires an FTS index to exist; returns empty if none available.
*
* Opens a fresh LanceDB snapshot for this call (mmnto/totem#1418).
*/
searchFts(options: SearchOptions): Promise;
/** Delete all chunks from a specific file (for incremental re-index). */
deleteByFile(filePath: string): Promise;
/** Drop the entire table. Used for full re-index. */
reset(): Promise;
/** Re-open the LanceDB connection, picking up rebuilt files after a full sync. */
reconnect(): Promise;
/**
* Open a fresh read snapshot (connection + table + fts flag) for a single
* query. Scoped to the caller, NOT written onto `this.db` / `this.table`,
* so concurrent searches don't race each other's handle lifetime
* (mmnto/totem#1418 Shield CRITICAL follow-up).
*
* **Why unconditional reopen.** The MCP server holds LanceStore for the
* life of the process. When `totem sync` (a separate process) rewrites
* `.lancedb/` files underneath us, LanceDB's in-memory manifest keeps
* pointing at the pre-sync snapshot. Vector search against that stale
* view returns empty, which falls through to FTS-only via the hybrid
* path, producing uniform ~0.016 RRF scores instead of real similarity
* ranks. The corrupt path is silent because no exception fires.
*
* **Why not mtime-check.** A benchmark (see scripts/bench-lance-open.ts)
* measured connect+openTable at ~0.5-1.1ms per call against real indexes.
* That's well under the 10ms threshold where mtime gating starts to pay
* for its own complexity. Reopening every time is strictly simpler and
* eliminates a whole category of cache-invalidation bugs.
*
* **Why per-call snapshots.** Assigning the fresh connection to `this.db`
* and then closing the old one from a second concurrent caller would
* invalidate the first caller's in-flight query. Instead each call holds
* its own connection + table references through the query lifetime, and
* closes the connection in a finally block after the results return.
* The instance-level `this.db` / `this.table` fields are still maintained
* so write paths and existing consumers (healthCheck, stats, count) see
* an up-to-date view on the next non-read call via `connect()`.
*
* **Why not also refresh write paths.** `totem sync` owns the writer
* connection exclusively inside a single process. Write operations
* (insert, deleteByFile, reset) already hold a consistent view for
* their own workflow and a mid-sequence reopen would drop the in-flight
* table reference that `insert()` sets during first-table creation.
*/
private openReadSnapshot;
/**
* Test-seam instrumentation (mmnto/totem#1418): total count of read-path
* handle refreshes since this store was constructed. Asserted by the
* stale-handle regression test to confirm every search() call reopens.
*/
get readRefreshCount(): number;
/** Return true if the table doesn't exist or has zero rows. */
isEmpty(): Promise;
/** Return the total number of rows in the store. */
count(): Promise;
/** Return stats about the current index. */
stats(): Promise<{
totalChunks: number;
byType: Record;
}>;
/** Run a health check against the index, verifying dimensions, search, and FTS. */
healthCheck(): Promise;
/**
* Returns one entry per distinct `filePath` in the store, with row counts
* and a derived `origin` (`@scope/pkg` or `pkg` when the path lives under
* `node_modules/`, otherwise `local`).
*
* `lastSynced` on every returned entry is set to the supplied `writtenAt`
* timestamp (or `new Date()` if omitted). LanceDB rows do not carry per-row
* sync timestamps in the current schema, so every document in a single
* manifest necessarily carries the same `lastSynced` value — callers
* building an `IndexManifest` should pass their `writtenAt` here so that
* `documents[].lastSynced === manifest.writtenAt` for that run.
*
* Output is sorted by `sourceFile` ascending so the manifest payload is
* deterministic across runs.
*/
manifestDocuments(writtenAt?: Date): Promise<{
sourceFile: string;
origin: string;
rowCount: number;
lastSynced: string;
}[]>;
/**
* Distinct raw `filePath` values currently in the store — the set of source
* files with at least one chunk. Returns paths AS STORED (NOT normalized),
* because callers purge via `deleteByFile`, which matches the stored literal;
* separator normalization for comparison is the caller's job
* (mmnto-ai/totem#2151 W1). Bounded `select(['filePath'])` projection — never
* pulls vectors or chunk bodies into memory, but reads O(chunks) rows and
* dedups in JS (same shape as `manifestDocuments`); a DB-level DISTINCT is
* tracked in mmnto-ai/totem#2175 for large stores.
*/
getDistinctPaths(): Promise;
}
//# sourceMappingURL=lance-store.d.ts.map