Skip to main content
Glama
apolosan

Design Patterns MCP Server

by apolosan
pattern-seeder.ts19.4 kB
/** * Pattern Data Seeder for Design Patterns MCP Server * Loads pattern data from JSON files and seeds the database */ import { DatabaseManager } from './database-manager.js'; import { Pattern } from '../models/pattern.js'; import { logger } from './logger.js'; import fs from 'fs'; import path from 'path'; interface SeederConfig { patternsPath: string; batchSize: number; skipExisting: boolean; } export class PatternSeeder { private db: DatabaseManager; private config: SeederConfig; constructor(db: DatabaseManager, config: SeederConfig) { this.db = db; this.config = config; } /** * Seed all pattern data */ async seedAll(): Promise<SeederResult> { const results: SeederResult[] = []; let totalPatterns = 0; let totalImplementations = 0; let totalRelationships = 0; try { // Get all pattern JSON files const patternFiles = await this.getPatternFiles(); // First pass: Load all patterns and collect relationships const allPatterns: Pattern[] = []; const allRelationships: Array<{ sourceId: string; relationship: any; filename: string }> = []; for (const file of patternFiles) { const data = await this.loadPatternFile(file); const patterns = data.patterns || (data.id ? [data] : []); for (const pattern of patterns) { allPatterns.push(pattern); // Collect relationships for later insertion const relatedPatterns = pattern.relatedPatterns || pattern.related_patterns; const relationships = pattern.relationships; // Process legacy relatedPatterns format if (relatedPatterns) { for (const rel of relatedPatterns) { allRelationships.push({ sourceId: pattern.id, relationship: rel, filename: file }); } } // Process new relationships format if (relationships) { for (const rel of relationships) { allRelationships.push({ sourceId: pattern.id, relationship: rel, filename: file }); } } } } // Second pass: Insert all patterns this.db.transaction(() => { for (const pattern of allPatterns) { const patternInserted = this.insertPattern(pattern); if (patternInserted) { totalPatterns++; } } }); // Third pass: Insert all implementations this.db.transaction(() => { for (const pattern of allPatterns) { if (pattern.implementations) { for (const impl of pattern.implementations) { const implInserted = this.insertImplementation(pattern.id, impl); if (implInserted) { totalImplementations++; } } } } }); // Fourth pass: Insert all relationships (after all patterns exist) this.db.transaction(() => { for (const { sourceId, relationship } of allRelationships) { // Handle both string and object formats for relationships if (typeof relationship === 'string') { // String format: relationship is the target pattern name const relInserted = this.insertRelationship(sourceId, relationship); if (relInserted) { totalRelationships++; } } else if (relationship && typeof relationship === 'object') { // Object format: extract target pattern name const targetPatternName = relationship.patternId || relationship.targetPatternId || relationship.name; if (targetPatternName) { const relInserted = this.insertRelationship(sourceId, targetPatternName); if (relInserted) { totalRelationships++; } } } } }); return { success: true, message: `Successfully seeded ${totalPatterns} patterns, ${totalImplementations} implementations, and ${totalRelationships} relationships`, totalPatterns, totalImplementations, totalRelationships, fileResults: results, }; } catch (error) { return { success: false, message: `Seeding failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error: error instanceof Error ? error : new Error('Unknown error'), }; } } /** * Seed patterns from a specific file */ async seedFromFile(filePath: string): Promise<SeederResult> { try { const data = await this.loadPatternFile(filePath); const patterns = data.patterns || []; let patternsInserted = 0; let implementationsInserted = 0; let relationshipsInserted = 0; // Process patterns in batches for (let i = 0; i < patterns.length; i += this.config.batchSize) { const batch = patterns.slice(i, i + this.config.batchSize); const batchResult = await this.seedBatch(batch); patternsInserted += batchResult.patternsInserted; implementationsInserted += batchResult.implementationsInserted; relationshipsInserted += batchResult.relationshipsInserted; } const filename = path.basename(filePath); return { success: true, message: `Seeded ${patternsInserted} patterns from ${filename}`, patternsInserted, implementationsInserted, relationshipsInserted, filename, }; } catch (error) { const filename = path.basename(filePath); return { success: false, message: `Failed to seed from ${filename}: ${error instanceof Error ? error.message : 'Unknown error'}`, error: error instanceof Error ? error : new Error('Unknown error'), filename, }; } } /** * Seed a batch of patterns */ private async seedBatch(patterns: Pattern[]): Promise<BatchResult> { let patternsInserted = 0; let implementationsInserted = 0; let relationshipsInserted = 0; // Collect all relationships for deferred insertion const allRelationships: Array<{ sourceId: string; relationship: any }> = []; // First pass: Insert all patterns and collect relationships this.db.transaction(() => { for (const pattern of patterns) { const patternInserted = this.insertPattern(pattern); if (patternInserted) { patternsInserted++; // Collect relationships for later insertion const relatedPatterns = pattern.relatedPatterns || pattern.related_patterns; if (relatedPatterns) { for (const rel of relatedPatterns) { allRelationships.push({ sourceId: pattern.id, relationship: rel }); } } } } }); // Second pass: Insert implementations this.db.transaction(() => { for (const pattern of patterns) { // Insert implementations if (pattern.implementations) { for (const impl of pattern.implementations) { const implInserted = this.insertImplementation(pattern.id, impl); if (implInserted) { implementationsInserted++; } } } } }); // Third pass: Insert relationships (after all patterns exist) this.db.transaction(() => { for (const { sourceId, relationship } of allRelationships) { // Handle both string and object formats for relationships if (typeof relationship === 'string') { // String format: relationship is the target pattern name const relInserted = this.insertRelationship(sourceId, relationship); if (relInserted) { relationshipsInserted++; } } else if (relationship && typeof relationship === 'object') { // Object format: extract target pattern name const targetPatternName = relationship.patternId || relationship.targetPatternId || relationship.name; if (targetPatternName) { const relInserted = this.insertRelationship(sourceId, targetPatternName); if (relInserted) { relationshipsInserted++; } } } } }); return { patternsInserted, implementationsInserted, relationshipsInserted, }; } /** * Insert a pattern into the database */ private insertPattern(pattern: Pattern): boolean { try { if (this.config.skipExisting) { const existing = this.db.queryOne('SELECT id FROM patterns WHERE id = ?', [pattern.id]); if (existing) { return false; // Skip existing } } const sql = ` INSERT OR REPLACE INTO patterns ( id, name, category, description, when_to_use, benefits, drawbacks, use_cases, complexity, tags, examples, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ pattern.id, pattern.name, pattern.category, pattern.description, JSON.stringify(pattern.when_to_use || []), JSON.stringify(pattern.benefits || []), JSON.stringify(pattern.drawbacks || []), JSON.stringify(pattern.use_cases || []), pattern.complexity, JSON.stringify(pattern.tags || []), pattern.examples ? JSON.stringify(pattern.examples) : null, (pattern.createdAt ? new Date(pattern.createdAt) : new Date()).toISOString(), (pattern.updatedAt ? new Date(pattern.updatedAt) : new Date()).toISOString(), ]; this.db.execute(sql, params); return true; } catch (error) { console.error(`Failed to insert pattern ${pattern.id}:`, error); return false; } } /** * Insert a pattern implementation */ private insertImplementation(patternId: string, implementation: any): boolean { try { const sql = ` INSERT OR REPLACE INTO pattern_implementations ( id, pattern_id, language, approach, code, explanation, dependencies, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ crypto.randomUUID(), patternId, implementation.language || 'unknown', implementation.approach || 'default', implementation.code || '', implementation.explanation || '', JSON.stringify(implementation.dependencies || []), new Date().toISOString(), ]; this.db.execute(sql, params); return true; } catch (error) { console.error(`Failed to insert implementation for pattern ${patternId}:`, error); return false; } } /** * Insert a pattern relationship */ private insertRelationship(sourcePatternId: string, relationship: any): boolean { try { let targetPatternId: string; let type: string; let strength: number; let description: string; // Handle different relationship formats if (typeof relationship === 'string') { // Legacy format: relationship is target pattern name targetPatternId = relationship; type = 'related'; strength = 1.0; description = `Related to ${relationship}`; } else if (relationship && typeof relationship === 'object') { // New format: relationship is an object targetPatternId = relationship.targetPatternId || relationship.patternId || relationship.name; type = relationship.type || 'related'; strength = relationship.strength || 1.0; description = relationship.description || `Related to ${targetPatternId}`; } else { console.warn(`Invalid relationship format for pattern ${sourcePatternId}:`, relationship); return false; } // Find target pattern ID by name if needed let actualTargetId: string; if (typeof targetPatternId === 'string' && targetPatternId.length > 0) { // Check if it's already an ID (assume IDs don't contain spaces and are lowercase) const isId = /^[a-z0-9_-]+$/.test(targetPatternId) && !targetPatternId.includes(' '); if (!isId) { // It's a pattern name, find the ID const targetPattern = this.db.queryOne<{ id: string }>( 'SELECT id FROM patterns WHERE name = ?', [targetPatternId] ); if (!targetPattern) { console.warn( `Target pattern not found: ${targetPatternId} (referenced by ${sourcePatternId})` ); return false; } actualTargetId = targetPattern.id; } else { // Assume it's already an ID actualTargetId = targetPatternId; } } else { console.warn(`Invalid targetPatternId for pattern ${sourcePatternId}:`, targetPatternId); return false; } // Check if relationship already exists const existing = this.db.queryOne( 'SELECT id FROM pattern_relationships WHERE source_pattern_id = ? AND target_pattern_id = ?', [sourcePatternId, actualTargetId] ); if (existing) { return false; // Skip duplicate } const sql = ` INSERT INTO pattern_relationships ( id, source_pattern_id, target_pattern_id, type, strength, description, created_at ) VALUES (?, ?, ?, ?, ?, ?, ?) `; const params = [ crypto.randomUUID(), sourcePatternId, actualTargetId, type, strength, description, new Date().toISOString(), ]; this.db.execute(sql, params); return true; } catch (error) { console.error(`Failed to insert relationship for pattern ${sourcePatternId}:`, error); return false; } } /** * Get all pattern files */ private async getPatternFiles(): Promise<string[]> { try { const files = fs .readdirSync(this.config.patternsPath) .filter(file => file.endsWith('.json')) .map(file => path.join(this.config.patternsPath, file)); return files; } catch (error) { console.error('Failed to read pattern files:', error); return []; } } /** * Load pattern data from file */ private async loadPatternFile(filePath: string): Promise<any> { try { const content = fs.readFileSync(filePath, 'utf8'); return JSON.parse(content); } catch (error) { console.error(`Failed to load pattern file ${filePath}:`, error); throw error; } } /** * Clear all pattern data */ async clearAll(): Promise<void> { this.db.transaction(() => { this.db.execute('DELETE FROM pattern_relationships'); this.db.execute('DELETE FROM pattern_implementations'); this.db.execute('DELETE FROM patterns'); }); logger.info('pattern-seeder', 'All pattern data cleared'); } /** * Get seeding statistics */ async getStats(): Promise<SeederStats> { const patternCount = this.db.queryOne<{ count: number }>( 'SELECT COUNT(*) as count FROM patterns' ); const implementationCount = this.db.queryOne<{ count: number }>( 'SELECT COUNT(*) as count FROM pattern_implementations' ); const relationshipCount = this.db.queryOne<{ count: number }>( 'SELECT COUNT(*) as count FROM pattern_relationships' ); const categoryStats = this.db.query<{ category: string; count: number }>( 'SELECT category, COUNT(*) as count FROM patterns GROUP BY category ORDER BY count DESC' ); const languageStats = this.db.query<{ language: string; count: number }>( 'SELECT language, COUNT(*) as count FROM pattern_implementations GROUP BY language ORDER BY count DESC' ); return { totalPatterns: patternCount?.count || 0, totalImplementations: implementationCount?.count || 0, totalRelationships: relationshipCount?.count || 0, patternsByCategory: categoryStats, implementationsByLanguage: languageStats, }; } /** * Validate seeded data */ async validate(): Promise<ValidationResult> { const errors: string[] = []; try { // Check for patterns without implementations const patternsWithoutImpl = this.db.query<{ id: string; name: string }>( `SELECT p.id, p.name FROM patterns p LEFT JOIN pattern_implementations pi ON p.id = pi.pattern_id WHERE pi.id IS NULL` ); if (patternsWithoutImpl.length > 0) { errors.push(`${patternsWithoutImpl.length} patterns have no implementations`); } // Check for orphaned implementations const orphanedImpl = this.db.query<{ id: string; pattern_id: string }>( `SELECT pi.id, pi.pattern_id FROM pattern_implementations pi LEFT JOIN patterns p ON pi.pattern_id = p.id WHERE p.id IS NULL` ); if (orphanedImpl.length > 0) { errors.push(`${orphanedImpl.length} implementations reference non-existent patterns`); } // Check for self-referencing relationships const selfRefs = this.db.query<{ id: string }>( `SELECT id FROM pattern_relationships WHERE source_pattern_id = target_pattern_id` ); if (selfRefs.length > 0) { errors.push(`${selfRefs.length} relationships are self-referencing`); } // Check for invalid relationship types const invalidTypes = this.db.query<{ type: string; count: number }>( `SELECT type, COUNT(*) as count FROM pattern_relationships WHERE type NOT IN ('similar', 'alternative', 'complementary', 'conflicting', 'evolves-to', 'specializes', 'generalizes', 'requires', 'extends', 'replaces', 'combines-with', 'contrasts-with') GROUP BY type` ); if (invalidTypes.length > 0) { errors.push(`${invalidTypes.length} relationships have invalid types`); } } catch (error) { errors.push(`Validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } return { valid: errors.length === 0, errors, }; } } interface SeederResult { success: boolean; message: string; patternsInserted?: number; implementationsInserted?: number; relationshipsInserted?: number; filename?: string; error?: Error; fileResults?: SeederResult[]; totalPatterns?: number; totalImplementations?: number; totalRelationships?: number; } interface BatchResult { patternsInserted: number; implementationsInserted: number; relationshipsInserted: number; } interface SeederStats { totalPatterns: number; totalImplementations: number; totalRelationships: number; patternsByCategory: Array<{ category: string; count: number }>; implementationsByLanguage: Array<{ language: string; count: number }>; } interface ValidationResult { valid: boolean; errors: string[]; } // Default seeder configuration const DEFAULT_SEEDER_CONFIG: SeederConfig = { patternsPath: './src/data/patterns', batchSize: 10, skipExisting: true, }; // Factory function for creating seeder export function createPatternSeeder( db: DatabaseManager, config?: Partial<SeederConfig> ): PatternSeeder { const finalConfig = { ...DEFAULT_SEEDER_CONFIG, ...config }; return new PatternSeeder(db, finalConfig); }

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/apolosan/design_patterns_mcp'

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