/** * SQL for searching through the core index, as Drizzle fragments an app * composes into its own query. The app keeps its own access rule, filters, * paging, and output; the index supplies matching and ranking. * * Matching, for each term: * - titles and summaries match anywhere, including mid-word ("prio" finds * "Task Priorities"), because they're short; * - bodies match whole words, every word as a prefix, through the GIN index; * mid-word body matches are deliberately not supported. A phrase needs * its words adjacent, except in a document too long or repetitive for * Postgres to keep every word position, where every word being in one * field is enough. * * Ranking reproduces the tiers the browser lane uses * (`TITLE_MATCH_TIER` in Content): exact title 5, title prefix 4, title word * prefixes 3, title substrings 2, title or summary 1, then how many query * groups the title and summary cover, then whether the body contains the * query as a phrase. Callers add their own tie-breaks. * * A query with a term longer than the index can match as a phrase throws * `SearchTermTooLongError`; answer it with the app's fallback search. */ import { type SQL } from "drizzle-orm"; import type { ParsedSearchQuery } from "./query-parser.js"; import type { SearchableResourceRegistration } from "./registry.js"; export interface IndexedSearchOptions { registration: SearchableResourceRegistration; query: Pick; /** "title" matches titles only. Defaults to title, summary, and body. */ fields?: "all" | "title"; } export interface IndexedSearchSql { /** Join target and condition: `.innerJoin(search.join, search.on)`. */ join: SQL; on: SQL; /** Every group matches and no negative does. */ match: SQL; matchTier: SQL; titleCoverage: SQL; summaryCoverage: SQL; bodyPhrase: SQL; /** The ranking terms above, best first, leaving out any that are always 0. */ orderBy: SQL[]; } export declare function indexedSearchSql(options: IndexedSearchOptions): IndexedSearchSql;