/** * Search indices and FTS5 (SPEC_D). Index registry in search_indices; * per-index tables created by FT.CREATE. FTS5 contentless + BM25 ranking. */ import type Database from './driver.ts'; export interface SearchField { name: string; type: string; } export interface IndexSchema { version: number; fields: SearchField[]; tokenizer: string; } export interface IndexMeta { name: string; schema_json: string; created_at: number; updated_at: number; schema: IndexSchema; } /** Server-level FT.SEARCH guards, modeled on RediSearch MINPREFIX, MAXSEARCHRESULTS and TIMEOUT. */ export interface SearchOptions { /** Minimum characters before `*` in a prefix term. Default 3. */ minPrefix?: number; /** Matches counted and ranked per query; the reported total stops here. Default 10000. */ maxResults?: number; /** Default per-query time budget in ms; 0 disables it. FT.SEARCH TIMEOUT overrides it. Default 500. */ timeoutMs?: number; } export type SearchLimits = Required; export declare class SearchTimeoutError extends Error { constructor(); } /** * Resolve FT.SEARCH guards. Throws on invalid values so misconfiguration fails at startup. */ export declare function resolveSearchLimits(opts?: SearchOptions): SearchLimits; /** * Validate index name (D.12.1). Throws if invalid. */ export declare function validateIndexName(name: string): void; export interface IndexCounts { num_docs: number; fts_rows: number; num_suggestions: number; } /** * Count every index now, so no client command pays for the first full count. * Full scans of each index: run once when the server starts. */ export declare function loadSearchCounts(db: Database): void; /** * Remove all RESP-visible search indices, documents and suggestions. * Migration bookkeeping is intentionally stored separately and is not touched. */ export declare function clearSearchData(db: Database): void; /** * Create a new search index. Atomic: registry + docs, docmap, fts, sugs tables. * @param name - Index name (must pass validateIndexName) * @param fields - At least one, must include payload TEXT */ export declare function createIndex(db: Database, name: string, fields: SearchField[]): void; /** * Get index metadata. Throws if index does not exist. */ export declare function getIndexMeta(db: Database, name: string): IndexMeta; /** * Add or replace a document. If doc exists and replace is false, throws ERR document exists. * @param fields - Field name -> value (must match schema) */ export declare function addDocument(db: Database, idx: string, docId: string, score: number, replace: boolean, fields: Record): void; /** * Delete document by doc_id. Returns 1 if deleted, 0 if not found. */ /** * Load stored fields for FT.GET: flat [field, value, ...] in schema field order. * RediSearch-compatible: empty string values encode as null in RESP. * @returns null if document is not in the index */ export declare function getDocumentFields(db: Database, idx: string, docId: string): (string | null)[] | null; export declare function deleteDocument(db: Database, idx: string, docId: string): number; export interface SearchQueryOptions { offset?: number; count?: number; noContent?: boolean; limits?: SearchLimits; /** Per-query time budget in ms (0 = none); defaults to limits.timeoutMs. */ timeoutMs?: number; /** Monotonic ms clock for the time budget. */ clock?: () => number; } /** * Search index. Returns { total, docIds, fieldsByDoc? }. * * Reads matches in one pass in index order, stopping after limits.maxResults * (the reported total is capped there) and ranking only what was read: * FTS5 bm25 is more negative for better matches, so lower bm25 * score ranks first. * The time budget is checked between rows; node:sqlite cannot interrupt a * running step, so FTS5 setup work before the first row is bounded only by minPrefix. */ export declare function search(db: Database, idx: string, query: string, options?: SearchQueryOptions): { total: number; docIds: string[]; fieldsByDoc: Record> | undefined; }; /** * Add or update suggestion. Returns 1 if inserted, 0 if updated. * @param incr - If true and exists, add to score */ export declare function suggestionAdd(db: Database, idx: string, term: string, score: number, incr: boolean, payload?: string): number; /** * Get suggestions by prefix. Returns array of terms (and optionally scores/payloads interleaved). */ export declare function suggestionGet(db: Database, idx: string, prefix: string, options?: { max?: number; withScores?: boolean; withPayloads?: boolean; }): (string | number)[]; /** * Delete suggestion by term. Returns 1 if deleted, 0 if not found. */ export declare function suggestionDel(db: Database, idx: string, term: string): number; /** * Get maintained counts for FT.INFO: num_docs, fts_rows, num_suggestions. */ export declare function getIndexCounts(db: Database, idx: string): IndexCounts;