Skip to main content
Glama
UniversalDeleteService.tsβ€’8.34 kB
/** * UniversalDeleteService - Centralized record deletion operations * * Extracted from shared-handlers.ts as part of Issue #489 Phase 3. * Provides universal delete functionality across all resource types. */ import { UniversalResourceType } from '../handlers/tool-configs/universal/types.js'; import type { UniversalDeleteParams } from '../handlers/tool-configs/universal/types.js'; import { isValidId } from '../utils/validation.js'; import { debug } from '../utils/logger.js'; // Import shared type definitions and utilities import { is404Error, createNotFoundError, type TaskError, type ApiErrorWithResponse, } from '../types/universal-service-types.js'; // Import delete functions for each resource type import { deleteCompany } from '../objects/companies/index.js'; import { deletePerson } from '../objects/people-write.js'; import { deleteList } from '../objects/lists.js'; import { deleteObjectRecord } from '../objects/records/index.js'; import { deleteTask, getTask } from '../objects/tasks.js'; import { deleteNote } from '../objects/notes.js'; import { shouldUseMockData } from './create/index.js'; /** * UniversalDeleteService provides centralized record deletion functionality * * **Type Safety Improvements**: This service now uses shared type definitions from * universal-service-types.ts to eliminate repeated inline types and improve * runtime safety through type guards. * * **Record<string, unknown> vs any**: Throughout this service, we use * Record<string, unknown> instead of any for better type safety. This allows * property access while preventing unsafe operations on unknown data structures. */ export class UniversalDeleteService { /** * Delete a record across any supported resource type * * @param params - Delete operation parameters * @returns Promise resolving to success status and record ID */ static async deleteRecord( params: UniversalDeleteParams ): Promise<{ success: boolean; record_id: string }> { const { resource_type, record_id } = params; switch (resource_type) { case UniversalResourceType.COMPANIES: try { await deleteCompany(record_id); return { success: true, record_id }; } catch (error: unknown) { // Map API errors to structured format using shared type guards if (is404Error(error)) { throw createNotFoundError( UniversalResourceType.COMPANIES, record_id ); } throw error; } case UniversalResourceType.PEOPLE: try { await deletePerson(record_id); return { success: true, record_id }; } catch (error: unknown) { // Map API errors to structured format using shared type guards if (is404Error(error)) { throw createNotFoundError(UniversalResourceType.PEOPLE, record_id); } throw error; } case UniversalResourceType.LISTS: await deleteList(record_id); return { success: true, record_id }; case UniversalResourceType.RECORDS: try { await deleteObjectRecord('records', record_id); return { success: true, record_id }; } catch (error: unknown) { // Map API errors to structured format using shared type guards if (is404Error(error)) { throw createNotFoundError(UniversalResourceType.RECORDS, record_id); } throw error; } case UniversalResourceType.DEALS: await deleteObjectRecord('deals', record_id); return { success: true, record_id }; case UniversalResourceType.TASKS: // In mock mode, pre-validate IDs and emit deterministic message expected by tests if (shouldUseMockData()) { if (!isValidId(record_id)) { /** * **Type Safety Note**: Using TaskError interface instead of any * to maintain type safety while preserving test compatibility. */ const err: TaskError = new Error(`Task not found: ${record_id}`); err.status = 404; err.body = { code: 'not_found', message: `Task not found: ${record_id}`, }; throw err; } if ( process.env.NODE_ENV === 'development' || process.env.VERBOSE_TESTS === 'true' ) { debug( 'UniversalDeleteService', '[MockInjection] Using mock data for task deletion' ); } return { success: true, record_id }; } try { const resp = await deleteTask(record_id); // deleteTask returns boolean - if false, treat as not found if (resp === false) { /** * **Type Safety Note**: Using TaskError interface for structured error * properties while maintaining Error base class for compatibility. */ const err: TaskError = new Error( `Task with ID "${record_id}" not found.` ); err.status = 404; err.body = { code: 'not_found', message: `Task with ID "${record_id}" not found.`, }; throw err; } return { success: true, record_id }; } catch (error: unknown) { // Map API errors to structured format, with a single retry for occasional eventual consistency if (is404Error(error)) { // Best-effort verification: if task still exists, wait briefly and retry once try { const exists = await getTask(record_id).then( () => true, () => false ); if (exists) { await new Promise((r) => setTimeout(r, 500)); const retried = await deleteTask(record_id); if (retried) return { success: true, record_id }; } } catch { // ignore and fall through to not_found mapping } /** * **Type Safety**: Using TaskError instead of any for structured error properties */ const err: TaskError = new Error( `Task with ID "${record_id}" not found.` ); err.status = 404; err.body = { code: 'not_found', message: `Task with ID "${record_id}" not found.`, }; throw err; // dispatcher should mark isError=true } // Map specific 400 errors for task ID validation to clearer messages /** * **Type Safety**: Using ApiErrorWithResponse interface instead of inline any casting * to provide proper type checking while maintaining flexibility for error handling. */ const apiErr = error as ApiErrorWithResponse; const status = apiErr?.response?.status ?? apiErr?.status; const errorMessage = ( apiErr?.response?.data?.message ?? apiErr?.message ?? '' ) .toString() .toLowerCase(); if (status === 400) { // Only map task_id related 400 errors to avoid masking other validation issues if ( errorMessage.includes('task_id') || errorMessage.includes('invalid task') || errorMessage.includes('malformed') ) { // Throw a plain Error so the MCP wrapper surfaces the message text (matches test regex) throw new Error(`Invalid request to delete task ${record_id}`); } // For other 400 errors, preserve original error to maintain validation visibility debug( 'UniversalDeleteService', `Preserving 400 error for task deletion (not task_id related): ${errorMessage}` ); } throw error; } case UniversalResourceType.NOTES: { const result = await deleteNote(record_id); return { success: result.success, record_id }; } default: throw new Error( `Unsupported resource type for delete: ${resource_type}` ); } } }

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