Skip to main content
Glama
search.ts7.3 kB
/** * Search functionality for companies * * Simplified implementation following CLAUDE.md documentation-first rule. * Uses standard Attio API patterns instead of custom workarounds. */ import { getLazyAttioClient } from '../../api/lazy-client.js'; import { searchObject, advancedSearchObject, ListEntryFilters, } from '../../api/operations/index.js'; import { createScopedLogger } from '@/utils/logger.js'; import { ResourceType, Company, FilterConditionType, } from '../../types/attio.js'; import { FilterValidationError, FilterErrorCategory, } from '../../errors/api-errors.js'; import { normalizeDomain } from '../../utils/domain-utils.js'; /** * Configuration options for company search */ export interface CompanySearchOptions { /** Maximum number of results to return */ maxResults?: number; } /** * Searches for companies by name using standard Attio API * * @param query - Search query string to match against company names * @param options - Optional search configuration * @returns Array of matching company objects */ export async function searchCompanies( query: string, options: CompanySearchOptions = {} ): Promise<Company[]> { if (!query || typeof query !== 'string' || !query.trim()) { return []; } if ( options.maxResults !== undefined && (typeof options.maxResults !== 'number' || options.maxResults < 0 || !Number.isInteger(options.maxResults)) ) { throw new Error('maxResults must be a non-negative integer'); } const results = await searchObject<Company>(ResourceType.COMPANIES, query); // Apply maxResults limit if specified return options.maxResults ? results.slice(0, options.maxResults) : results; } /** * Searches for companies by domain using correct 'domains' field * * @param domain - Domain to search for * @returns Array of matching company objects */ export async function searchCompaniesByDomain( domain: string ): Promise<Company[]> { if (!domain || typeof domain !== 'string' || !domain.trim()) { return []; } const normalizedDomain = normalizeDomain(domain); const api = getLazyAttioClient(); try { const response = await api.post('/objects/companies/records/query', { filter: { domains: { $contains: normalizedDomain }, }, }); return response?.data?.data || []; } catch (error: unknown) { // Use structured logging with sanitized error information const logger = createScopedLogger( 'companies-search', 'searchCompaniesByDomain' ); logger.error(`Domain search failed for domain`, error, { domain: normalizedDomain, }); return []; } } /** * Performs advanced search with custom filters using standard API * * @param filters - List of filters to apply * @param limit - Maximum number of results to return * @param offset - Number of results to skip * @returns Array of company results */ export async function advancedSearchCompanies( filters: ListEntryFilters, limit?: number, offset?: number ): Promise<Company[]> { // Strict validation BEFORE calling advancedSearchObject // This ensures FilterValidationError is thrown for invalid inputs if (!filters) { throw new FilterValidationError( 'Filters object is required', FilterErrorCategory.STRUCTURE ); } if (!('filters' in filters)) { throw new FilterValidationError( 'Filters must include a "filters" array', FilterErrorCategory.STRUCTURE ); } if (!Array.isArray(filters.filters)) { throw new FilterValidationError( 'Filters.filters must be an array', FilterErrorCategory.STRUCTURE ); } // Validate each filter condition structure if (filters.filters && filters.filters.length > 0) { filters.filters.forEach((filter, index) => { if (!filter || typeof filter !== 'object') { throw new FilterValidationError( `Invalid condition at index ${index}: filter must be an object`, FilterErrorCategory.STRUCTURE ); } if (!filter.attribute) { throw new FilterValidationError( `Invalid condition at index ${index}: missing attribute object`, FilterErrorCategory.ATTRIBUTE ); } if (!filter.attribute.slug) { throw new FilterValidationError( `Invalid condition at index ${index}: missing attribute.slug property`, FilterErrorCategory.ATTRIBUTE ); } if (!filter.condition) { throw new FilterValidationError( `Invalid condition at index ${index}: missing condition property`, FilterErrorCategory.CONDITION ); } // Additional validation for unknown operators/malformed structures if (typeof filter.condition !== 'string') { throw new FilterValidationError( `Invalid condition at index ${index}: condition must be a string`, FilterErrorCategory.CONDITION ); } }); } if ( limit !== undefined && (typeof limit !== 'number' || limit < 0 || !Number.isInteger(limit)) ) { throw new FilterValidationError( 'Limit must be a non-negative integer', FilterErrorCategory.VALUE ); } if ( offset !== undefined && (typeof offset !== 'number' || offset < 0 || !Number.isInteger(offset)) ) { throw new FilterValidationError( 'Offset must be a non-negative integer', FilterErrorCategory.VALUE ); } return await advancedSearchObject<Company>( ResourceType.COMPANIES, filters, limit, offset ); } /** * Helper function to create filters for searching companies by name */ export function createNameFilter( name: string, condition: FilterConditionType = FilterConditionType.CONTAINS ): ListEntryFilters { if (!name || typeof name !== 'string') { throw new FilterValidationError( 'Name parameter must be a non-empty string', FilterErrorCategory.VALUE ); } return { filters: [ { attribute: { slug: 'name' }, condition: condition, value: name, }, ], }; } /** * Helper function to create filters for searching companies by domain */ export function createDomainFilter( domain: string, condition: FilterConditionType = FilterConditionType.CONTAINS ): ListEntryFilters { if (!domain || typeof domain !== 'string') { throw new FilterValidationError( 'Domain parameter must be a non-empty string', FilterErrorCategory.VALUE ); } const normalizedDomain = normalizeDomain(domain); return { filters: [ { attribute: { slug: 'domains' }, condition: condition, value: normalizedDomain, }, ], }; } /** * Helper function to create filters for searching companies by industry */ export function createIndustryFilter( industry: string, condition: FilterConditionType = FilterConditionType.CONTAINS ): ListEntryFilters { if (!industry || typeof industry !== 'string') { throw new FilterValidationError( 'Industry parameter must be a non-empty string', FilterErrorCategory.VALUE ); } return { filters: [ { attribute: { slug: 'industry' }, condition: condition, value: industry, }, ], }; }

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