Skip to main content
Glama
batch-companies.tsβ€’16.5 kB
/** * Batch operations for company records */ import { ResourceType, Company, BatchResponse, BatchConfig, RecordAttributes, RecordBatchCreateParams, RecordBatchUpdateParams, } from '../types/attio.js'; import { CompanyFieldValue } from '../types/tool-types.js'; import { createScopedLogger } from '@/utils/logger.js'; import { executeBatchOperations, batchCreateRecords, batchUpdateRecords, } from '../api/operations/index.js'; import { createCompany, updateCompany, deleteCompany, searchCompanies, getCompanyDetails, } from './companies/index.js'; import { CompanyValidator } from '../validators/company-validator.js'; import { validateBatchOperation } from '../utils/batch-validation.js'; type CompanyUpdatePayload = { id: string; attributes: RecordAttributes; }; type CompanyOperationInput = | { type: 'create'; data: RecordAttributes } | { type: 'update'; data: CompanyUpdatePayload } | { type: 'delete'; data: string }; type BatchFunctionParams = unknown; /** * Helper function to execute a batch operation with improved error handling * * This function centralizes batch operations for companies, providing consistent * error handling, proper object type setting, and fallback to individual operations * when the batch API is unavailable. * * @template T - The type of input records (e.g., RecordAttributes for create, {id, attributes} for update) * @template R - The type of output records (typically Company) * @param operationType - The type of operation (create, update, delete, etc.) * @param records - The records to process * @param batchFunction - The batch API function to call * @param singleFunction - The single-record fallback function * @param batchConfig - Optional batch configuration * @returns Batch response with results for each record and summary statistics * @throws Error if records is not an array or validation fails */ async function executeBatchCompanyOperation<T, R>( operationType: 'create' | 'update' | 'delete' | 'search' | 'get', records: T[], batchFunction: (params: BatchFunctionParams) => Promise<R[]>, singleFunction: (params: T) => Promise<R>, batchConfig?: Partial<BatchConfig> ): Promise<BatchResponse<R>> { // Validation check if (!Array.isArray(records) || records.length === 0) { throw new Error( `Invalid ${operationType} parameters: records must be a non-empty array` ); } // Validate batch operation for DoS protection const validation = validateBatchOperation({ items: records, operationType, resourceType: ResourceType.COMPANIES, checkPayload: operationType === 'create' || operationType === 'update', }); if (!validation.isValid) { throw new Error(validation.error); } try { // Attempt to use the batch API const payload = operationType === 'create' ? { objectSlug: ResourceType.COMPANIES, records: records.map((record) => ({ attributes: record })), } : { objectSlug: ResourceType.COMPANIES, records, }; const results = await batchFunction(payload); // Format the response return { results: results.map((result, index) => ({ id: `${operationType}_company_${index}`, success: true, data: result, })), summary: { total: records.length, succeeded: results.length, failed: records.length - results.length, }, }; } catch (error: unknown) { // Log the error for debugging console.error( `[batchCompany${ operationType.charAt(0).toUpperCase() + operationType.slice(1) }] ` + `Batch API failed with error: ${ error instanceof Error ? error.message : String(error) }` ); // Fall back to individual operations return executeBatchOperations<T, R>( records.map((record, index) => ({ id: `${operationType}_company_${index}`, params: record, })), singleFunction, batchConfig ); } } /** * Creates multiple company records in batch operation * * This function creates multiple company records in a single API call, with automatic * fallback to individual operations if the batch API is unavailable. All input data * is validated before processing. * * @example * ```typescript * // Create multiple companies * const companies = [ * { name: "Acme Corp", website: "https://acme.com", industry: "Technology" }, * { name: "Umbrella Inc", website: "https://umbrella.com", industry: "Manufacturing" } * ]; * * const result = await batchCreateCompanies({ companies }); * console.error(`Created ${result.summary.succeeded} of ${result.summary.total} companies`); * ``` * * Note on parameter structure: * This function expects a different parameter structure compared to generic record batch operations. * While generic batch operations take (objectSlug, records, objectId), company-specific batch operations * expect an object with {companies, config} to match the structure expected by the MCP tool schema. * * @param params - Object containing array of companies and optional config * @returns Batch response with created companies */ export async function batchCreateCompanies(params: { companies: RecordAttributes[]; config?: Partial<BatchConfig>; }): Promise<BatchResponse<Company>> { // Early validation of parameters - fail fast if (!params) { throw new Error('Invalid request: params object is required'); } // Extract and validate companies array const { companies, config: batchConfig } = params; if (!companies) { throw new Error("Invalid request: 'companies' parameter is required"); } if (!Array.isArray(companies)) { throw new Error("Invalid request: 'companies' parameter must be an array"); } if (companies.length === 0) { throw new Error("Invalid request: 'companies' array cannot be empty"); } // Validate array contents - ensure each item is a valid object companies.forEach((company, index) => { if (!company || typeof company !== 'object') { throw new Error( `Invalid company data at index ${index}: must be a non-null object` ); } // Basic validation: name property must exist (but can be empty - that will be handled gracefully) if (!('name' in company)) { throw new Error( `Invalid company data at index ${index}: 'name' is required` ); } }); try { // Use the generic batch create with graceful validation // Attempt validation for each company, but allow individual failures const validatedCompanies = await Promise.all( companies.map(async (company, index) => { try { return await CompanyValidator.validateCreate( company as Record<string, CompanyFieldValue> ); } catch (error: unknown) { // Log validation error but allow operation to continue for individual handling console.warn( `Validation failed for company at index ${index}:`, error instanceof Error ? error.message : String(error) ); return company; // Pass through for individual handling in fallback operations } }) ); // Use the shared helper function for consistent handling return executeBatchCompanyOperation<RecordAttributes, Company>( 'create', validatedCompanies, (params: unknown) => batchCreateRecords(params as RecordBatchCreateParams), createCompany, batchConfig ); } catch (error: unknown) { if (error instanceof Error && error.message.includes('validation')) { // Re-throw validation errors with more context throw new Error(`Company validation failed: ${error.message}`); } // For other errors, log and then rethrow // Error occurred during batch creation throw error; } } /** * Updates multiple company records in batch operation * * This function updates multiple company records in a single API call, with automatic * fallback to individual operations if the batch API is unavailable. It performs extensive * validation to ensure all required fields are present and properly formatted. * * @example * ```typescript * // Update multiple companies * const updates = [ * { id: "3bdf5c9d-aa78-492a-a4c1-5a143e94ef0e", attributes: { industry: "New Industry" } }, * { id: "e252e8df-d6b6-4909-a03c-6c9f144c4580", attributes: { website: "https://new-site.com" } } * ]; * * const result = await batchUpdateCompanies({ updates }); * console.error(`Updated ${result.summary.succeeded} of ${result.summary.total} companies`); * ``` * * Note on parameter structure: * This function expects a different parameter structure compared to generic record batch operations. * While generic batch operations take (objectSlug, records, objectId), company-specific batch operations * expect an object with {updates, config} to match the structure expected by the MCP tool schema. * * @param params - Object containing array of updates and optional config * @returns Batch response with updated companies */ export async function batchUpdateCompanies(params: { updates: Array<{ id: string; attributes: RecordAttributes }>; config?: Partial<BatchConfig>; }): Promise<BatchResponse<Company>> { // Early validation of parameters - fail fast if (!params) { throw new Error('Invalid request: params object is required'); } // Extract and validate updates array const { updates, config: batchConfig } = params; if (!updates) { throw new Error("Invalid request: 'updates' parameter is required"); } if (!Array.isArray(updates)) { throw new Error("Invalid request: 'updates' parameter must be an array"); } if (updates.length === 0) { throw new Error("Invalid request: 'updates' array cannot be empty"); } // Validate array contents - ensure each item has required fields updates.forEach((update, index) => { if (!update || typeof update !== 'object') { throw new Error( `Invalid update data at index ${index}: must be a non-null object` ); } if (!update.id) { throw new Error( `Invalid update data at index ${index}: 'id' is required` ); } if (!update.attributes || typeof update.attributes !== 'object') { throw new Error( `Invalid update data at index ${index}: 'attributes' must be a non-null object` ); } }); try { // Use the shared helper function for consistent handling return executeBatchCompanyOperation< { id: string; attributes: RecordAttributes }, Company >( 'update', updates, (params: unknown) => batchUpdateRecords(params as RecordBatchUpdateParams), (params) => updateCompany(params.id, params.attributes), batchConfig ); } catch (error: unknown) { // Enhanced error handling with more context if (error instanceof Error) { if (error.message.includes('not found')) { throw new Error( `Company update failed: One or more company IDs do not exist` ); } else { // Provide more detailed error throw new Error(`Company batch update failed: ${error.message}`); } } // For other errors, log and then rethrow using structured logging const logger = createScopedLogger( 'batch-companies', 'batchUpdateCompanies' ); logger.error('Batch company update failed', error, { updateCount: params.updates.length, }); throw error; } } /** * Deletes multiple company records in batch * * @param companyIds - Array of company IDs to delete * @param batchConfig - Optional batch configuration * @returns Batch response with deletion results */ export async function batchDeleteCompanies( companyIds: string[], batchConfig?: Partial<BatchConfig> ): Promise<BatchResponse<boolean>> { // The Attio API doesn't have a batch delete endpoint, so use individual operations return executeBatchOperations<string, boolean>( companyIds.map((id, index) => ({ id: `delete_company_${index}`, params: id, })), (params) => deleteCompany(params), batchConfig ); } /** * Performs batch searches for companies by name * * @param queries - Array of search query strings * @param batchConfig - Optional batch configuration * @returns Batch response with search results for each query */ export async function batchSearchCompanies( queries: string[], batchConfig?: Partial<BatchConfig> ): Promise<BatchResponse<Company[]>> { return executeBatchOperations<string, Company[]>( queries.map((query, index) => ({ id: `search_companies_${index}`, params: query, })), (params) => searchCompanies(params), batchConfig ); } /** * Gets details for multiple companies in batch * * @param companyIds - Array of company IDs or URIs to fetch * @param batchConfig - Optional batch configuration * @returns Batch response with company details for each ID */ export async function batchGetCompanyDetails( companyIds: string[], batchConfig?: Partial<BatchConfig> ): Promise<BatchResponse<Company>> { return executeBatchOperations<string, Company>( companyIds.map((id, index) => ({ id: `get_company_details_${index}`, params: id, })), (params) => getCompanyDetails(params), batchConfig ); } /** * Performs mixed batch operations on companies * * @param operations - Array of mixed operations (create, update, delete) * @param batchConfig - Optional batch configuration * @returns Batch response with results for each operation */ export async function batchCompanyOperations( operations: CompanyOperationInput[], batchConfig?: Partial<BatchConfig> ): Promise<BatchResponse<Company | boolean>> { const results: Array<{ id: string; success: boolean; data?: Company | boolean; error?: unknown; }> = []; let succeeded = 0; let failed = 0; // Process operations with chunking const config = { maxBatchSize: 10, continueOnError: true, ...batchConfig, }; const chunks: CompanyOperationInput[][] = []; for (let i = 0; i < operations.length; i += config.maxBatchSize) { chunks.push(operations.slice(i, i + config.maxBatchSize)); } for (const chunk of chunks) { const chunkResults = await Promise.allSettled( chunk.map(async (operation, index) => { const opId = `${operation.type}_company_${index}`; try { let result: Company | boolean; if (operation.type === 'create') { result = await createCompany(operation.data); } else if (operation.type === 'update') { result = await updateCompany( operation.data.id, operation.data.attributes ); } else if (operation.type === 'delete') { result = await deleteCompany(operation.data); } else { // Type guard to safely extract the type from unknown operation const operationType = operation && typeof operation === 'object' && 'type' in operation ? String((operation as Record<string, unknown>).type) : 'unknown'; throw new Error(`Unknown operation type: ${operationType}`); } succeeded++; return { id: opId, success: true, data: result, }; } catch (error: unknown) { failed++; if (!config.continueOnError) { throw error; } return { id: opId, success: false, error, }; } }) ); // Collect results chunkResults.forEach((result, index) => { if (result.status === 'fulfilled') { results.push(result.value); } else { results.push({ id: `operation_${index}`, success: false, error: result.reason, }); } }); } return { results, summary: { total: operations.length, succeeded, failed, }, }; } /** * Helper to create batch operation items for companies */ export function createBatchItems<T>( items: T[], prefix: string = 'item' ): Array<{ id: string; data: T }> { return items.map((item, index) => ({ id: `${prefix}_${index}`, data: item, })); }

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