Skip to main content
Glama
UniversalMetadataService.tsβ€’9.78 kB
/** * UniversalMetadataService - Metadata orchestration facade * * Rebuilt for Issue #648 to delegate responsibilities to specialised metadata services * while preserving the existing API surface for universal handlers. */ import { UniversalResourceType } from '../handlers/tool-configs/universal/types.js'; import type { UniversalAttributesParams } from '../handlers/tool-configs/universal/types.js'; import type { JsonObject } from '../types/attio.js'; import { getCompanyAttributes, discoverCompanyAttributes, } from '../objects/companies/index.js'; import { getListAttributes } from '../objects/lists.js'; import { createDefaultMetadataServices, MetadataServicesDeps, MetadataDiscoveryService, MetadataRecordService, MetadataTransformService, MetadataMetricsService, } from './metadata/index.js'; import type { DiscoverTypeOptions, DiscoveryMetricsSummary, MetricsFilter, } from './metadata/index.js'; class UniversalMetadataFacade { constructor(private readonly deps: MetadataServicesDeps) {} private get discoveryService(): MetadataDiscoveryService { return this.deps.discoveryService; } private get recordService(): MetadataRecordService { return this.deps.recordService; } private get transformService(): MetadataTransformService { return this.deps.transformService; } private get metricsService(): MetadataMetricsService { return this.deps.metricsService; } async discoverAttributesForResourceType( resourceType: UniversalResourceType, options?: { categories?: string[]; objectSlug?: string; useCache?: boolean; cacheTtl?: number; } ): Promise<JsonObject> { const discoverOptions: DiscoverTypeOptions = { resourceType, categories: options?.categories, objectSlug: options?.objectSlug, useCache: options?.useCache, cacheTtl: options?.cacheTtl, }; return this.discoveryService.discoverForType(discoverOptions); } async discoverTaskAttributes(options?: { categories?: string[]; }): Promise<JsonObject> { return this.discoveryService.discoverTasks(options); } async getAttributesForRecord( resourceType: UniversalResourceType, recordId: string ): Promise<JsonObject> { return this.recordService.getAttributesForRecord(resourceType, recordId); } filterAttributesByCategory( attributes: Record<string, unknown> | unknown[], categories?: string[] ): Record<string, unknown> | unknown[] { return this.transformService.filterByCategory(attributes, categories); } async getAttributes(params: UniversalAttributesParams): Promise<JsonObject> { const { resource_type, record_id, categories } = params; let result: JsonObject; switch (resource_type) { case UniversalResourceType.COMPANIES: { if (record_id) { result = await getCompanyAttributes(record_id); } else { result = await discoverCompanyAttributes(); } break; } case UniversalResourceType.PEOPLE: { if (record_id) { result = await this.recordService.getAttributesForRecord( resource_type, record_id ); } else { result = await this.discoverAttributesForResourceType(resource_type, { categories, }); } break; } case UniversalResourceType.LISTS: { result = await getListAttributes(); break; } case UniversalResourceType.RECORDS: { if (record_id) { result = await this.recordService.getAttributesForRecord( resource_type, record_id ); } else { result = await this.discoverAttributesForResourceType(resource_type, { categories, }); } break; } case UniversalResourceType.DEALS: { if (record_id) { result = await this.recordService.getAttributesForRecord( resource_type, record_id ); } else { result = await this.discoverAttributesForResourceType(resource_type, { categories, }); } break; } case UniversalResourceType.TASKS: { if (record_id) { result = await this.recordService.getAttributesForRecord( resource_type, record_id ); } else { result = await this.discoverAttributesForResourceType(resource_type, { categories, }); } break; } default: throw new Error( `Unsupported resource type for get attributes: ${resource_type}` ); } const filtered = this.transformService.filterByCategory(result, categories); return filtered as JsonObject; } async discoverAttributes( resource_type: UniversalResourceType, options?: { categories?: string[]; objectSlug?: string; } ): Promise<JsonObject> { switch (resource_type) { case UniversalResourceType.COMPANIES: { const res = await discoverCompanyAttributes(); if (!options?.categories?.length) { return res; } return this.transformService.filterByCategory( res, options.categories ) as JsonObject; } case UniversalResourceType.PEOPLE: return this.discoverAttributesForResourceType(resource_type, options); case UniversalResourceType.LISTS: return getListAttributes(); case UniversalResourceType.RECORDS: { if (options?.objectSlug) { return this.discoverAttributesForResourceType(resource_type, { categories: options.categories, objectSlug: options.objectSlug, }); } return this.discoverAttributesForResourceType(resource_type, options); } case UniversalResourceType.DEALS: { const baseResult = await this.discoverAttributesForResourceType( resource_type, options ); const { FIELD_MAPPINGS } = await import( '../handlers/tool-configs/universal/field-mapper.js' ); const dealsMapping = FIELD_MAPPINGS[UniversalResourceType.DEALS]; if (dealsMapping?.fieldMappings) { const mappings: Record<string, string> = {}; Object.entries(dealsMapping.fieldMappings).forEach( ([displayName, apiField]) => { if (apiField) { mappings[displayName] = apiField; } } ); mappings['Deal name'] = 'name'; mappings['Deal stage'] = 'stage'; mappings['Deal value'] = 'value'; mappings['Associated company'] = 'associated_company'; return { ...baseResult, mappings: { ...(baseResult.mappings as JsonObject | undefined), ...mappings, }, note: 'Use mappings to convert display names to API field names for create-record', }; } return baseResult; } case UniversalResourceType.TASKS: return this.discoverAttributesForResourceType(resource_type, options); default: throw new Error( `Unsupported resource type for discover attributes: ${resource_type}` ); } } getPerformanceMetrics(options?: MetricsFilter): DiscoveryMetricsSummary { return this.metricsService.getMetrics(options); } clearPerformanceMetrics(): void { this.metricsService.clear(); } } export class UniversalMetadataService { private static facade = new UniversalMetadataFacade( createDefaultMetadataServices() ); /** * Replace the underlying facade (useful for testing custom dependencies) */ static useFacade(facade: UniversalMetadataFacade): void { this.facade = facade; } /** * Reset the facade back to default dependencies */ static reset(): void { this.facade = new UniversalMetadataFacade(createDefaultMetadataServices()); } static async discoverAttributesForResourceType( resourceType: UniversalResourceType, options?: { categories?: string[]; objectSlug?: string; useCache?: boolean; cacheTtl?: number; } ): Promise<Record<string, unknown>> { return this.facade.discoverAttributesForResourceType(resourceType, options); } static async discoverTaskAttributes(options?: { categories?: string[]; }): Promise<Record<string, unknown>> { return this.facade.discoverTaskAttributes(options); } static async getAttributesForRecord( resourceType: UniversalResourceType, recordId: string ): Promise<Record<string, unknown>> { return this.facade.getAttributesForRecord(resourceType, recordId); } static filterAttributesByCategory( attributes: Record<string, unknown> | unknown[], categories?: string[] ): Record<string, unknown> | unknown[] { return this.facade.filterAttributesByCategory(attributes, categories); } static async getAttributes( params: UniversalAttributesParams ): Promise<Record<string, unknown>> { return this.facade.getAttributes(params); } static async discoverAttributes( resource_type: UniversalResourceType, options?: { categories?: string[]; objectSlug?: string; } ): Promise<Record<string, unknown>> { return this.facade.discoverAttributes(resource_type, options); } static getPerformanceMetrics( options?: MetricsFilter ): DiscoveryMetricsSummary { return this.facade.getPerformanceMetrics(options); } static clearPerformanceMetrics(): void { this.facade.clearPerformanceMetrics(); } } export { UniversalMetadataFacade };

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