Skip to main content
Glama
data-merger.ts7.81 kB
/** * Data Merger * * Handles intelligent merging of data from multiple providers for read operations. * Removes duplicates, combines unique fields, and maintains data integrity. */ import { ProviderResult } from '../providers/types.js'; export class DataMerger { /** * Merges list operation results from multiple providers * Removes duplicates based on name/id fields and combines arrays */ static mergeListResults(results: ProviderResult[]): any[] { const allItems: any[] = []; const seen = new Set<string>(); // Collect all items from successful results for (const result of results) { if (result.success && result.data) { const items = Array.isArray(result.data) ? result.data : [result.data]; for (const item of items) { const key = this.getItemKey(item); if (!seen.has(key)) { seen.add(key); // Add provider information to each item allItems.push({ ...item, _providers: [result.provider] }); } else { // Item already exists, add provider to existing item const existingItem = allItems.find(i => this.getItemKey(i) === key); if (existingItem && !existingItem._providers.includes(result.provider)) { existingItem._providers.push(result.provider); } } } } } // Sort by updated_at or created_at if available return this.sortItemsByDate(allItems); } /** * Merges get operation results from multiple providers * Combines unique fields from different providers into a single object */ static mergeGetResults(results: ProviderResult[]): any { if (results.length === 0) return null; if (results.length === 1) return results[0].success ? results[0].data : null; // Start with the first successful result let mergedData: any = null; const providers: string[] = []; for (const result of results) { if (result.success && result.data) { providers.push(result.provider); if (!mergedData) { mergedData = { ...result.data }; } else { // Merge unique fields from this provider mergedData = this.mergeObjects(mergedData, result.data, result.provider); } } } if (mergedData) { mergedData._providers = providers; mergedData._mergeInfo = { totalProviders: results.length, successfulProviders: providers.length, merged: true }; } return mergedData; } /** * Merges search operation results from multiple providers * Combines search results, removes duplicates, and ranks by relevance */ static mergeSearchResults(results: ProviderResult[]): any[] { const allItems: any[] = []; const seen = new Set<string>(); // Collect all items from successful results for (const result of results) { if (result.success && result.data) { const items = Array.isArray(result.data) ? result.data : [result.data]; for (const item of items) { const key = this.getItemKey(item); if (!seen.has(key)) { seen.add(key); // Add provider information and search metadata allItems.push({ ...item, _providers: [result.provider], _search: { provider: result.provider, relevance: this.calculateRelevance(item, result.provider) } }); } else { // Item already exists, add provider to existing item const existingItem = allItems.find(i => this.getItemKey(i) === key); if (existingItem) { if (!existingItem._providers.includes(result.provider)) { existingItem._providers.push(result.provider); } // Update search info if this provider has better relevance const newRelevance = this.calculateRelevance(item, result.provider); if (newRelevance > existingItem._search.relevance) { existingItem._search = { provider: result.provider, relevance: newRelevance }; } } } } } } // Sort by relevance score, then by date return allItems.sort((a, b) => { // Higher relevance first if (b._search.relevance !== a._search.relevance) { return b._search.relevance - a._search.relevance; } // Then by date if available return this.compareByDate(a, b); }); } /** * Generates a unique key for an item based on common identifier fields */ private static getItemKey(item: any): string { // Try different identifier fields in order of preference const possibleKeys = [ item.id, item.name, item.full_name, item.title, `${item.owner?.login || item.owner}/${item.name || item.repo}`, item.sha, // for commits item.number, // for issues/PRs JSON.stringify(item) // fallback ]; for (const key of possibleKeys) { if (key && typeof key === 'string') { return key; } } return Math.random().toString(36); // ultimate fallback } /** * Merges two objects, preserving unique fields from each provider */ private static mergeObjects(baseObj: any, newObj: any, provider: string): any { const merged = { ...baseObj }; for (const [key, value] of Object.entries(newObj)) { // If field doesn't exist in base, add it if (!(key in merged)) { merged[key] = value; merged[`_${key}_${provider}`] = value; // Also keep provider-specific version } // If field exists but is different, keep both versions else if (merged[key] !== value) { merged[`_${key}_${provider}`] = value; merged[`_${key}_conflict`] = true; } } return merged; } /** * Calculates relevance score for search results */ private static calculateRelevance(item: any, provider: string): number { let score = 0; // Base score by provider (GitHub slightly preferred) if (provider === 'github') { score += 1; } // Score by item type if (item.stargazers_count !== undefined) { score += Math.min(item.stargazers_count / 100, 10); // Max 10 points } if (item.forks_count !== undefined) { score += Math.min(item.forks_count / 50, 5); // Max 5 points } if (item.updated_at) { const daysSinceUpdate = (Date.now() - new Date(item.updated_at).getTime()) / (1000 * 60 * 60 * 24); score += Math.max(0, 5 - daysSinceUpdate / 30); // Max 5 points, decays over time } return score; } /** * Sorts items by date field (updated_at, created_at, etc.) */ private static sortItemsByDate(items: any[]): any[] { return items.sort((a, b) => this.compareByDate(a, b)); } /** * Compares two items by date fields */ private static compareByDate(a: any, b: any): number { const dateFields = ['updated_at', 'created_at', 'pushed_at', 'committed_date']; for (const field of dateFields) { const dateA = a[field] ? new Date(a[field]).getTime() : 0; const dateB = b[field] ? new Date(b[field]).getTime() : 0; if (dateA !== dateB) { return dateB - dateA; // Newest first } } return 0; } /** * Removes duplicate items from an array based on a field */ static removeDuplicates(items: any[], field: string): any[] { const seen = new Set(); return items.filter(item => { const value = item[field]; if (seen.has(value)) { return false; } seen.add(value); return true; }); } }

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/Andre-Buzeli/git-mcp'

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