mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
b91d9e9a72
* feat: Extract search into pluggable provider system Refactors the monolithic SearchHelper into a pluggable search provider architecture, enabling alternative search backends (Elasticsearch, Turbopuffer, etc.) while preserving PostgreSQL full-text search as the default. The SEARCH_PROVIDER env var selects the active provider. - Add BaseSearchProvider abstract class and SearchProviderManager - Add Hook.SearchProvider to the plugin system - Move PostgreSQL search logic into plugins/postgres-search/ - Add SearchIndexProcessor for event-driven index sync - Update all callers to use the provider manager directly - Keep SearchHelper as a deprecated thin wrapper for backwards compat Closes #11347 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: Remove deprecated SearchHelper wrapper All callers now use SearchProviderManager directly, so the thin delegation wrapper is no longer needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: Rename postgres-search plugin to search-postgres Renames the plugin folder and id so that future search provider plugins (e.g. search-elasticsearch, search-turbopuffer) will be colocated alphabetically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: Remove special-case plugin import from SearchProviderManager Make PluginManager.loadPlugins resilient to individual plugin load failures so SearchProviderManager can use the standard getHooks path without needing to directly import the search-postgres plugin. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: Add missing search provider tests for full coverage parity Adds all tests that existed in the old SearchHelper.test.ts but were missing from PostgresSearchProvider.test.ts, including searchTitlesForUser status filters, collection filtering, group memberships, and sorting tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feedback --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
150 lines
4.6 KiB
TypeScript
150 lines
4.6 KiB
TypeScript
import type { DateFilter } from "@shared/types";
|
|
import type { SearchableModel } from "@shared/types";
|
|
import type { DirectionFilter, SortFilter, StatusFilter } from "@shared/types";
|
|
import type Collection from "@server/models/Collection";
|
|
import type Comment from "@server/models/Comment";
|
|
import type Document from "@server/models/Document";
|
|
import type Share from "@server/models/Share";
|
|
import type Team from "@server/models/Team";
|
|
import type User from "@server/models/User";
|
|
|
|
export interface SearchResponse {
|
|
results: {
|
|
/** The search ranking, for sorting results. */
|
|
ranking: number;
|
|
/** A snippet of contextual text around the search result. */
|
|
context?: string;
|
|
/** The document result. */
|
|
document: Document;
|
|
}[];
|
|
/** The total number of results for the search query without pagination. */
|
|
total: number;
|
|
}
|
|
|
|
export interface SearchOptions {
|
|
/** The query limit for pagination. */
|
|
limit?: number;
|
|
/** The query offset for pagination. */
|
|
offset?: number;
|
|
/** The text to search for. */
|
|
query?: string;
|
|
/** Limit results to a collection. Authorization is presumed to have been done before passing to this provider. */
|
|
collectionId?: string | null;
|
|
/** Limit results to a shared document. */
|
|
share?: Share;
|
|
/** Limit results to a date range. */
|
|
dateFilter?: DateFilter;
|
|
/** Status of the documents to return. */
|
|
statusFilter?: StatusFilter[];
|
|
/** Limit results to a list of documents. */
|
|
documentIds?: string[];
|
|
/** Limit results to a list of users that collaborated on the document. */
|
|
collaboratorIds?: string[];
|
|
/** The minimum number of words to be returned in the contextual snippet. */
|
|
snippetMinWords?: number;
|
|
/** The maximum number of words to be returned in the contextual snippet. */
|
|
snippetMaxWords?: number;
|
|
/** The field to sort results by. */
|
|
sort?: SortFilter;
|
|
/** The sort direction. */
|
|
direction?: DirectionFilter;
|
|
/** Whether to boost results by popularity score. Defaults to true. */
|
|
usePopularityBoost?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Abstract base class for search providers. Implementations handle full-text
|
|
* search, title search, collection search, and index management.
|
|
*/
|
|
export abstract class BaseSearchProvider {
|
|
/** Unique identifier for this provider, matched against `SEARCH_PROVIDER` env var. */
|
|
abstract id: string;
|
|
|
|
/**
|
|
* Perform a full-text search scoped to a user's accessible documents.
|
|
*
|
|
* @param user - the user performing the search.
|
|
* @param options - search options.
|
|
* @returns search results with ranking and context.
|
|
*/
|
|
abstract searchForUser(
|
|
user: User,
|
|
options?: SearchOptions
|
|
): Promise<SearchResponse>;
|
|
|
|
/**
|
|
* Perform a full-text search scoped to a team (used for shared document search).
|
|
*
|
|
* @param team - the team to search within.
|
|
* @param options - search options.
|
|
* @returns search results with ranking and context.
|
|
*/
|
|
abstract searchForTeam(
|
|
team: Team,
|
|
options?: SearchOptions
|
|
): Promise<SearchResponse>;
|
|
|
|
/**
|
|
* Search document titles for a user (used for link suggestions, quick search).
|
|
*
|
|
* @param user - the user performing the search.
|
|
* @param options - search options.
|
|
* @returns matching documents.
|
|
*/
|
|
abstract searchTitlesForUser(
|
|
user: User,
|
|
options?: SearchOptions
|
|
): Promise<Document[]>;
|
|
|
|
/**
|
|
* Search collections for a user.
|
|
*
|
|
* @param user - the user performing the search.
|
|
* @param options - search options.
|
|
* @returns matching collections.
|
|
*/
|
|
abstract searchCollectionsForUser(
|
|
user: User,
|
|
options?: SearchOptions
|
|
): Promise<Collection[]>;
|
|
|
|
/**
|
|
* Index or re-index a searchable item. For providers that rely on database
|
|
* triggers (e.g. PostgreSQL tsvector), this may be a no-op.
|
|
*
|
|
* @param model - the type of model being indexed.
|
|
* @param item - the model instance to index.
|
|
*/
|
|
abstract index(
|
|
model: SearchableModel,
|
|
item: Document | Collection | Comment
|
|
): Promise<void>;
|
|
|
|
/**
|
|
* Remove an item from the search index.
|
|
*
|
|
* @param model - the type of model being removed.
|
|
* @param id - the id of the item to remove.
|
|
* @param teamId - the team id the item belongs to.
|
|
*/
|
|
abstract remove(
|
|
model: SearchableModel,
|
|
id: string,
|
|
teamId: string
|
|
): Promise<void>;
|
|
|
|
/**
|
|
* Update metadata for an indexed item without re-indexing the full content.
|
|
* Useful for permission changes, moves, archive/unarchive.
|
|
*
|
|
* @param model - the type of model being updated.
|
|
* @param id - the id of the item to update.
|
|
* @param metadata - the metadata fields to update.
|
|
*/
|
|
abstract updateMetadata(
|
|
model: SearchableModel,
|
|
id: string,
|
|
metadata: Record<string, unknown>
|
|
): Promise<void>;
|
|
}
|