/** * Search index management for full-text search. * * Provides functions for building and maintaining an inverted index * that maps tokens to entity IDs for fast text search queries. * * Follows the same Ref-based pattern as index-manager.ts for consistency. */ import { Effect, Ref } from "effect"; import type { SearchIndexMap } from "../types/search-types.js"; /** * Entity constraint: must have a readonly string `id` field. */ type HasId = { readonly id: string; }; /** * Build a search index from entities for the specified fields. * * Creates an inverted index mapping tokens to sets of entity IDs. * For each entity, tokenizes the values of the specified fields * and adds the entity's ID to each token's set. * * @param fields - The fields to index for full-text search * @param entities - All entities in the collection * @returns Effect producing a Ref containing the SearchIndexMap * * @example * ```ts * const books = [ * { id: "1", title: "Dune", author: "Frank Herbert" }, * { id: "2", title: "Neuromancer", author: "William Gibson" }, * ] * * const indexRef = yield* buildSearchIndex(["title", "author"], books) * // Index structure: * // "dune" -> Set(["1"]) * // "frank" -> Set(["1"]) * // "herbert" -> Set(["1"]) * // "neuromancer" -> Set(["2"]) * // "william" -> Set(["2"]) * // "gibson" -> Set(["2"]) * ``` */ export declare const buildSearchIndex: (fields: ReadonlyArray, entities: ReadonlyArray) => Effect.Effect>; /** * Lookup candidate entity IDs from the search index for a given query. * * For each query token: * - Finds exact matches in the index * - Finds prefix matches (index tokens that start with the query token) * * Returns the intersection of ID sets across all query tokens (AND semantics). * If a query token has no matches, returns an empty set. * If queryTokens is empty, returns an empty set. * * @param indexRef - Ref containing the SearchIndexMap * @param queryTokens - Tokenized query terms to search for * @returns Effect producing a Set of candidate entity IDs * * @example * ```ts * // Index: "dune" -> Set(["1"]), "frank" -> Set(["1"]), "neuromancer" -> Set(["2"]) * * // Exact match * const ids = yield* lookupSearchIndex(indexRef, ["dune"]) * // → Set(["1"]) * * // Prefix match * const ids = yield* lookupSearchIndex(indexRef, ["neuro"]) * // → Set(["2"]) (matches "neuromancer") * * // Multi-token (AND semantics) * const ids = yield* lookupSearchIndex(indexRef, ["dune", "frank"]) * // → Set(["1"]) (intersection) * * // No match * const ids = yield* lookupSearchIndex(indexRef, ["xyz"]) * // → Set([]) * ``` */ export declare const lookupSearchIndex: (indexRef: Ref.Ref, queryTokens: ReadonlyArray) => Effect.Effect>; /** * Resolve candidate entities using the search index when a search query is present. * * Checks if the where clause contains a $search operator (field-level or top-level). * If found and the search index covers the queried fields, uses the index to * narrow the candidate set before full filtering. * * Returns undefined if: * - No $search operator is present * - The search index doesn't cover the queried fields * - The search index is empty * * @param where - The where clause from the query * @param searchIndexRef - The search index Ref (or undefined if not configured) * @param searchIndexFields - The fields covered by the search index (or undefined) * @param map - The entity data map (id -> entity) * @returns Effect producing Array if index was used, undefined if no usable index */ export declare const resolveWithSearchIndex: (where: Record | undefined, searchIndexRef: Ref.Ref | undefined, searchIndexFields: ReadonlyArray | undefined, map: ReadonlyMap) => Effect.Effect | undefined>; /** * Add an entity to the search index. * * Tokenizes the entity's indexed fields and adds the entity ID to each * token's set in the inverted index. This should be called after creating * a new entity to keep the search index up to date. * * @param indexRef - Ref containing the SearchIndexMap * @param entity - The entity to add to the index * @param fields - The fields to index for full-text search * @returns Effect that completes when the entity is added * * @example * ```ts * const newBook = { id: "5", title: "Snow Crash", author: "Neal Stephenson" } * yield* addToSearchIndex(indexRef, newBook, ["title", "author"]) * // Index now contains: * // "snow" -> Set([..., "5"]) * // "crash" -> Set([..., "5"]) * // "neal" -> Set([..., "5"]) * // "stephenson" -> Set([..., "5"]) * ``` */ export declare const addToSearchIndex: (indexRef: Ref.Ref, entity: T, fields: ReadonlyArray) => Effect.Effect; /** * Remove an entity from the search index. * * Tokenizes the entity's indexed fields and removes the entity ID from each * token's set in the inverted index. Cleans up empty sets to avoid memory * leaks. This should be called after deleting an entity to keep the search * index up to date. * * @param indexRef - Ref containing the SearchIndexMap * @param entity - The entity to remove from the index * @param fields - The fields that were indexed for full-text search * @returns Effect that completes when the entity is removed * * @example * ```ts * const bookToDelete = { id: "5", title: "Snow Crash", author: "Neal Stephenson" } * yield* removeFromSearchIndex(indexRef, bookToDelete, ["title", "author"]) * // Index now has entity "5" removed from: * // "snow", "crash", "neal", "stephenson" sets * // Empty sets are deleted from the index * ``` */ export declare const removeFromSearchIndex: (indexRef: Ref.Ref, entity: T, fields: ReadonlyArray) => Effect.Effect; /** * Update an entity in the search index. * * Efficiently handles updates by only reindexing fields that have changed. * For changed fields, removes old tokens and adds new tokens. This should * be called after updating an entity to keep the search index up to date. * * Optimization: If a field's value hasn't changed, no index operations are * performed for that field. This is more efficient than a full remove+add * when only some indexed fields are modified. * * @param indexRef - Ref containing the SearchIndexMap * @param oldEntity - The entity before the update * @param newEntity - The entity after the update * @param fields - The fields that are indexed for full-text search * @returns Effect that completes when the index is updated * * @example * ```ts * const oldBook = { id: "5", title: "Snow Crash", author: "Neal Stephenson" } * const newBook = { id: "5", title: "Snow Crash (Revised)", author: "Neal Stephenson" } * yield* updateInSearchIndex(indexRef, oldBook, newBook, ["title", "author"]) * // Only "title" changed, so: * // - Removes "5" from "snow", "crash" * // - Adds "5" to "snow", "crash", "revised" * // - "author" field is unchanged, no operations needed * ``` */ export declare const updateInSearchIndex: (indexRef: Ref.Ref, oldEntity: T, newEntity: T, fields: ReadonlyArray) => Effect.Effect; export {}; //# sourceMappingURL=search-index.d.ts.map