Skip to main content
Glama
PeopleSearchStrategy.tsβ€’7.64 kB
/** * People search strategy implementation * Issue #574: Extract people search logic from UniversalSearchService */ import { AttioRecord } from '../../types/attio.js'; import { SearchType, MatchType, SortType, UniversalResourceType, } from '../../handlers/tool-configs/universal/types.js'; import { BaseSearchStrategy } from './BaseSearchStrategy.js'; import { SearchStrategyParams, StrategyDependencies } from './interfaces.js'; import { FilterValidationError } from '../../errors/api-errors.js'; import { buildPeopleQueryFilters } from './query-filter-builder.js'; import { createScopedLogger } from '../../utils/logger.js'; /** * Search strategy for people with advanced filtering, name/email search, and content search */ export class PeopleSearchStrategy extends BaseSearchStrategy { constructor(dependencies: StrategyDependencies) { super(dependencies); } getResourceType(): string { return UniversalResourceType.PEOPLE; } supportsAdvancedFiltering(): boolean { return true; } supportsQuerySearch(): boolean { return true; } async search(params: SearchStrategyParams): Promise<AttioRecord[]> { const { query, filters, limit, offset, search_type = SearchType.BASIC, fields, match_type = MatchType.PARTIAL, sort = SortType.NAME, timeframeParams, } = params; // Apply timeframe filtering const enhancedFilters = this.applyTimeframeFiltering( filters, timeframeParams ); // If we have filters, use advanced search if (enhancedFilters) { return this.searchWithFilters(enhancedFilters, limit, offset); } // If we have a query, handle different search types if (query && query.trim().length > 0) { return this.searchWithQuery( query.trim(), search_type, fields, match_type, sort, limit, offset ); } // No query and no filters - return paginated results return this.searchWithoutQuery(limit, offset); } /** * Search people using advanced filters */ private async searchWithFilters( filters: Record<string, unknown>, limit?: number, offset?: number ): Promise<AttioRecord[]> { if (!this.dependencies.paginatedSearchFunction) { throw new Error('People search function not available'); } try { // FilterValidationError will bubble up naturally from searchFn, including for invalid empty filters const paginatedResult = await this.dependencies.paginatedSearchFunction( filters, { limit, offset, } ); return paginatedResult.results; } catch (error: unknown) { // Let FilterValidationError bubble up for proper error handling if (error instanceof FilterValidationError) { throw error; } throw error; } } /** * Search people with a text query */ private async searchWithQuery( query: string, searchType: SearchType, fields?: string[], matchType: MatchType = MatchType.PARTIAL, sort: SortType = SortType.NAME, limit?: number, offset?: number ): Promise<AttioRecord[]> { if (!this.dependencies.paginatedSearchFunction) { throw new Error('People search function not available'); } // Handle different search types if (searchType === SearchType.CONTENT) { return this.searchByContent( query, fields, matchType, sort, limit, offset ); } else { // For simple text queries without special fields/filters, // use searchObject which includes fast path optimization const { searchObject } = await import('../../api/operations/search.js'); const { ResourceType } = await import('../../types/attio.js'); // Calculate total records needed: offset + limit const start = offset || 0; const effectiveLimit = limit ? start + limit : undefined; const results = await searchObject(ResourceType.PEOPLE, query, { limit: effectiveLimit, }); // Apply offset to slice the results const end = limit ? start + limit : undefined; return results.slice(start, end); } } /** * Search people without any query or filters */ private async searchWithoutQuery( limit?: number, offset?: number ): Promise<AttioRecord[]> { if (!this.dependencies.paginatedSearchFunction) { throw new Error('People search function not available'); } return this.handleEmptyFilters( async (filters, limitArg, offsetArg) => { const paginatedResult = await this.dependencies .paginatedSearchFunction!(filters, { limit: limitArg, offset: offsetArg, }); return paginatedResult.results; }, limit, offset ); } /** * Search people by email address */ private async searchByEmail( query: string, limit?: number, offset?: number ): Promise<AttioRecord[]> { const emailFilters = { filters: [ { attribute: { slug: 'email_addresses' }, condition: 'contains', value: query, }, ], }; if (!this.dependencies.paginatedSearchFunction) { throw new Error('People search function not available'); } const paginatedResult = await this.dependencies.paginatedSearchFunction( emailFilters, { limit, offset, } ); return paginatedResult.results; } /** * Search people by content across multiple fields */ private async searchByContent( query: string, fields?: string[], matchType: MatchType = MatchType.PARTIAL, sort: SortType = SortType.NAME, limit?: number, offset?: number ): Promise<AttioRecord[]> { // Default content fields for people const searchFields = fields && fields.length > 0 ? fields : ['name', 'notes', 'email_addresses', 'job_title']; const contentFilters = this.createContentFilters( query, searchFields, matchType ); if (!this.dependencies.paginatedSearchFunction) { throw new Error('People search function not available'); } const paginatedResult = await this.dependencies.paginatedSearchFunction( contentFilters, { limit, offset, } ); // Apply relevance ranking if requested const results = this.applyRelevanceRanking( paginatedResult.results, query, searchFields, sort ); return results; } /** * Search people by name and email (basic search) */ private async searchByNameAndEmail( query: string, matchType: MatchType = MatchType.PARTIAL, limit?: number, offset?: number ): Promise<AttioRecord[]> { const nameEmailFilters = { filters: [ { attribute: { slug: 'name' }, condition: matchType === MatchType.EXACT ? 'equals' : 'contains', value: query, }, { attribute: { slug: 'email_addresses' }, condition: matchType === MatchType.EXACT ? 'equals' : 'contains', value: query, }, ], matchAny: true, // Use OR logic to match either name or email }; if (!this.dependencies.paginatedSearchFunction) { throw new Error('People search function not available'); } const paginatedResult = await this.dependencies.paginatedSearchFunction( nameEmailFilters, { limit, offset, } ); return paginatedResult.results; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server