feat: add comprehensive performance benchmark tracking system
- Create benchmark test suites for critical operations: - Node loading performance - Database query performance - Search operations performance - Validation performance - MCP tool execution performance - Add GitHub Actions workflow for benchmark tracking: - Runs on push to main and PRs - Uses github-action-benchmark for historical tracking - Comments on PRs with performance results - Alerts on >10% performance regressions - Stores results in GitHub Pages - Create benchmark infrastructure: - Custom Vitest benchmark configuration - JSON reporter for CI results - Result formatter for github-action-benchmark - Performance threshold documentation - Add supporting utilities: - SQLiteStorageService for benchmark database setup - MCPEngine wrapper for testing MCP tools - Test factories for generating benchmark data - Enhanced NodeRepository with benchmark methods - Document benchmark system: - Comprehensive benchmark guide in docs/BENCHMARKS.md - Performance thresholds in .github/BENCHMARK_THRESHOLDS.md - README for benchmarks directory - Integration with existing test suite The benchmark system will help monitor performance over time and catch regressions before they reach production. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,17 @@
|
||||
import { DatabaseAdapter } from './database-adapter';
|
||||
import { ParsedNode } from '../parsers/node-parser';
|
||||
import { SQLiteStorageService } from '../services/sqlite-storage-service';
|
||||
|
||||
export class NodeRepository {
|
||||
constructor(private db: DatabaseAdapter) {}
|
||||
private db: DatabaseAdapter;
|
||||
|
||||
constructor(dbOrService: DatabaseAdapter | SQLiteStorageService) {
|
||||
if ('db' in dbOrService) {
|
||||
this.db = dbOrService.db;
|
||||
} else {
|
||||
this.db = dbOrService;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save node with proper JSON serialization
|
||||
@@ -91,4 +100,145 @@ export class NodeRepository {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Additional methods for benchmarks
|
||||
upsertNode(node: ParsedNode): void {
|
||||
this.saveNode(node);
|
||||
}
|
||||
|
||||
getNodeByType(nodeType: string): any {
|
||||
return this.getNode(nodeType);
|
||||
}
|
||||
|
||||
getNodesByCategory(category: string): any[] {
|
||||
const rows = this.db.prepare(`
|
||||
SELECT * FROM nodes WHERE category = ?
|
||||
ORDER BY display_name
|
||||
`).all(category) as any[];
|
||||
|
||||
return rows.map(row => this.parseNodeRow(row));
|
||||
}
|
||||
|
||||
searchNodes(query: string, mode: 'OR' | 'AND' | 'FUZZY' = 'OR', limit: number = 20): any[] {
|
||||
let sql = '';
|
||||
const params: any[] = [];
|
||||
|
||||
if (mode === 'FUZZY') {
|
||||
// Simple fuzzy search
|
||||
sql = `
|
||||
SELECT * FROM nodes
|
||||
WHERE node_type LIKE ? OR display_name LIKE ? OR description LIKE ?
|
||||
ORDER BY display_name
|
||||
LIMIT ?
|
||||
`;
|
||||
const fuzzyQuery = `%${query}%`;
|
||||
params.push(fuzzyQuery, fuzzyQuery, fuzzyQuery, limit);
|
||||
} else {
|
||||
// OR/AND mode
|
||||
const words = query.split(/\s+/).filter(w => w.length > 0);
|
||||
const conditions = words.map(() =>
|
||||
'(node_type LIKE ? OR display_name LIKE ? OR description LIKE ?)'
|
||||
);
|
||||
const operator = mode === 'AND' ? ' AND ' : ' OR ';
|
||||
|
||||
sql = `
|
||||
SELECT * FROM nodes
|
||||
WHERE ${conditions.join(operator)}
|
||||
ORDER BY display_name
|
||||
LIMIT ?
|
||||
`;
|
||||
|
||||
for (const word of words) {
|
||||
const searchTerm = `%${word}%`;
|
||||
params.push(searchTerm, searchTerm, searchTerm);
|
||||
}
|
||||
params.push(limit);
|
||||
}
|
||||
|
||||
const rows = this.db.prepare(sql).all(...params) as any[];
|
||||
return rows.map(row => this.parseNodeRow(row));
|
||||
}
|
||||
|
||||
getAllNodes(limit?: number): any[] {
|
||||
let sql = 'SELECT * FROM nodes ORDER BY display_name';
|
||||
if (limit) {
|
||||
sql += ` LIMIT ${limit}`;
|
||||
}
|
||||
|
||||
const rows = this.db.prepare(sql).all() as any[];
|
||||
return rows.map(row => this.parseNodeRow(row));
|
||||
}
|
||||
|
||||
getNodeCount(): number {
|
||||
const result = this.db.prepare('SELECT COUNT(*) as count FROM nodes').get() as any;
|
||||
return result.count;
|
||||
}
|
||||
|
||||
getAIToolNodes(): any[] {
|
||||
return this.getAITools();
|
||||
}
|
||||
|
||||
getNodesByPackage(packageName: string): any[] {
|
||||
const rows = this.db.prepare(`
|
||||
SELECT * FROM nodes WHERE package_name = ?
|
||||
ORDER BY display_name
|
||||
`).all(packageName) as any[];
|
||||
|
||||
return rows.map(row => this.parseNodeRow(row));
|
||||
}
|
||||
|
||||
searchNodeProperties(nodeType: string, query: string, maxResults: number = 20): any[] {
|
||||
const node = this.getNode(nodeType);
|
||||
if (!node || !node.properties) return [];
|
||||
|
||||
const results: any[] = [];
|
||||
const searchLower = query.toLowerCase();
|
||||
|
||||
function searchProperties(properties: any[], path: string[] = []) {
|
||||
for (const prop of properties) {
|
||||
if (results.length >= maxResults) break;
|
||||
|
||||
const currentPath = [...path, prop.name || prop.displayName];
|
||||
const pathString = currentPath.join('.');
|
||||
|
||||
if (prop.name?.toLowerCase().includes(searchLower) ||
|
||||
prop.displayName?.toLowerCase().includes(searchLower) ||
|
||||
prop.description?.toLowerCase().includes(searchLower)) {
|
||||
results.push({
|
||||
path: pathString,
|
||||
property: prop,
|
||||
description: prop.description
|
||||
});
|
||||
}
|
||||
|
||||
// Search nested properties
|
||||
if (prop.options) {
|
||||
searchProperties(prop.options, currentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
searchProperties(node.properties);
|
||||
return results;
|
||||
}
|
||||
|
||||
private parseNodeRow(row: any): any {
|
||||
return {
|
||||
nodeType: row.node_type,
|
||||
displayName: row.display_name,
|
||||
description: row.description,
|
||||
category: row.category,
|
||||
developmentStyle: row.development_style,
|
||||
package: row.package_name,
|
||||
isAITool: Number(row.is_ai_tool) === 1,
|
||||
isTrigger: Number(row.is_trigger) === 1,
|
||||
isWebhook: Number(row.is_webhook) === 1,
|
||||
isVersioned: Number(row.is_versioned) === 1,
|
||||
version: row.version,
|
||||
properties: this.safeJsonParse(row.properties_schema, []),
|
||||
operations: this.safeJsonParse(row.operations, []),
|
||||
credentials: this.safeJsonParse(row.credentials_required, []),
|
||||
hasDocumentation: !!row.documentation
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user