supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
515 lines (506 loc) • 22.1 kB
JavaScript
;
/**
* Constraint Debugging and Testing Utilities for Epic 1: Universal MakerKit Core System
* Comprehensive debugging tools for constraint handling and validation
* Part of Task 1.5.5: Create constraint debugging and testing utilities
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConstraintDebuggingUtilities = void 0;
const logger_1 = require("../../core/utils/logger");
const constraint_handlers_1 = require("./constraint-handlers");
const multi_table_constraint_resolver_1 = require("../../schema/multi-table-constraint-resolver");
const slug_management_system_1 = require("../../schema/slug-management-system");
/**
* Comprehensive Constraint Debugging Utilities
*/
class ConstraintDebuggingUtilities {
constructor(client) {
this.activeSessions = new Map();
this.performanceMetrics = new Map();
this.client = client;
this.multiTableResolver = new multi_table_constraint_resolver_1.MultiTableConstraintResolver(client);
this.slugManager = new slug_management_system_1.AdvancedSlugManager(client);
}
/**
* Start a comprehensive constraint debugging session
*/
async startDebuggingSession(tableName, constraints, testData) {
const sessionId = this.generateSessionId();
const session = {
sessionId,
startTime: new Date(),
tableName,
constraints,
testData,
results: [],
summary: {
totalConstraints: constraints.length,
totalTests: 0,
successfulHandling: 0,
failedHandling: 0,
bypassRequired: 0,
averageExecutionTime: 0,
issueDistribution: {},
handlerUsageStats: {},
recommendations: []
}
};
this.activeSessions.set(sessionId, session);
logger_1.Logger.info(`Started constraint debugging session: ${sessionId} for table: ${tableName}`);
return sessionId;
}
/**
* Run comprehensive constraint tests
*/
async runConstraintTests(sessionId) {
const session = this.activeSessions.get(sessionId);
if (!session) {
throw new Error(`Debugging session not found: ${sessionId}`);
}
logger_1.Logger.info(`Running constraint tests for session: ${sessionId}`);
for (let dataIndex = 0; dataIndex < session.testData.length; dataIndex++) {
const testData = session.testData[dataIndex];
for (const constraint of session.constraints) {
const startTime = Date.now();
try {
const result = await this.testSingleConstraint(constraint, testData, session.tableName);
const executionTime = Date.now() - startTime;
const testResult = {
constraintName: constraint.constraintName,
constraintType: constraint.constraintType,
testDataIndex: dataIndex,
originalData: { ...testData },
result,
handlerUsed: this.identifyHandlerUsed(constraint, result),
executionTime,
issues: this.analyzeConstraintIssues(constraint, result, testData),
recommendations: this.generateRecommendations(constraint, result, testData)
};
session.results.push(testResult);
this.updatePerformanceMetrics(testResult);
this.updateSessionSummary(session, testResult);
}
catch (error) {
logger_1.Logger.error(`Error testing constraint ${constraint.constraintName}:`, error);
session.results.push({
constraintName: constraint.constraintName,
constraintType: constraint.constraintType,
testDataIndex: dataIndex,
originalData: { ...testData },
result: {
success: false,
originalData: testData,
modifiedData: testData,
appliedFixes: [],
warnings: [],
errors: [error.message],
bypassRequired: true
},
handlerUsed: 'error',
executionTime: Date.now() - startTime,
issues: [{
severity: 'critical',
type: 'handler_not_found',
message: `Error during constraint testing: ${error.message}`,
suggestedFix: 'Review constraint configuration and handler implementation'
}],
recommendations: ['Investigate constraint testing error']
});
}
}
}
this.generateSessionRecommendations(session);
logger_1.Logger.info(`Completed constraint tests for session: ${sessionId}`);
return session;
}
/**
* Test a single constraint with specific data
*/
async testSingleConstraint(constraint, data, tableName) {
const constraintType = constraint.constraintType || this.inferConstraintType(constraint);
return constraint_handlers_1.ConstraintHandlers.handleConstraint(constraint, constraintType, data);
}
/**
* Analyze constraint handling for issues
*/
analyzeConstraintIssues(constraint, result, originalData) {
const issues = [];
// Check for critical failures
if (!result.success) {
issues.push({
severity: 'critical',
type: 'constraint_violation',
message: 'Constraint handling failed',
suggestedFix: 'Review constraint logic and data compatibility',
relatedConstraints: [constraint.constraintName]
});
}
// Check if bypass is required
if (result.bypassRequired) {
issues.push({
severity: 'high',
type: 'handler_not_found',
message: 'No suitable handler found, bypass required',
suggestedFix: 'Implement specific handler for this constraint pattern',
relatedConstraints: [constraint.constraintName]
});
}
// Check for data quality issues
if (result.appliedFixes.length > 3) {
issues.push({
severity: 'medium',
type: 'data_quality',
message: 'Multiple fixes applied - data quality concerns',
suggestedFix: 'Review data generation patterns for better quality',
relatedConstraints: [constraint.constraintName]
});
}
// Check for business logic issues
if (this.isMakerKitConstraint(constraint)) {
const businessLogicIssues = this.analyzeMakerKitBusinessLogic(constraint, result, originalData);
issues.push(...businessLogicIssues);
}
// Check for performance issues
const performanceMetrics = this.performanceMetrics.get(constraint.constraintName);
if (performanceMetrics && performanceMetrics.averageExecutionTime > 100) {
issues.push({
severity: 'medium',
type: 'performance',
message: 'Slow constraint processing detected',
suggestedFix: 'Optimize constraint handler implementation',
relatedConstraints: [constraint.constraintName]
});
}
return issues;
}
/**
* Analyze MakerKit-specific business logic issues
*/
analyzeMakerKitBusinessLogic(constraint, result, originalData) {
const issues = [];
// Check personal account slug constraint
if (constraint.constraintName?.toLowerCase().includes('accounts_slug_null_if_personal')) {
if (originalData.is_personal_account === true && originalData.slug !== null) {
const hasSlugFix = result.appliedFixes.some(fix => fix.field === 'slug');
if (!hasSlugFix) {
issues.push({
severity: 'high',
type: 'business_logic',
message: 'Personal account slug constraint not properly handled',
suggestedFix: 'Ensure slug is set to null for personal accounts',
affectedField: 'slug'
});
}
}
}
// Check organization membership constraints
if (constraint.constraintName?.toLowerCase().includes('organization_member')) {
if (originalData.organization_id && originalData.user_id) {
if (!result.appliedFixes.some(fix => fix.type === 'add_dependency')) {
issues.push({
severity: 'medium',
type: 'business_logic',
message: 'Organization membership relationship may need validation',
suggestedFix: 'Verify organization membership constraints'
});
}
}
}
return issues;
}
/**
* Generate recommendations for constraint handling
*/
generateRecommendations(constraint, result, originalData) {
const recommendations = [];
// Success recommendations
if (result.success && result.appliedFixes.length === 0) {
recommendations.push('Constraint handled successfully without modifications');
}
// Fix recommendations
if (result.appliedFixes.length > 0) {
const fixTypes = new Set(result.appliedFixes.map(fix => fix.type));
if (fixTypes.has('set_field')) {
recommendations.push('Consider improving data generation to reduce field corrections');
}
if (fixTypes.has('add_dependency')) {
recommendations.push('Multi-table dependencies detected - ensure proper creation order');
}
}
// Performance recommendations
const metrics = this.performanceMetrics.get(constraint.constraintName);
if (metrics && metrics.averageExecutionTime > 50) {
recommendations.push('Consider optimizing constraint handler for better performance');
}
// MakerKit-specific recommendations
if (this.isMakerKitConstraint(constraint)) {
recommendations.push(...this.generateMakerKitRecommendations(constraint, result, originalData));
}
return recommendations;
}
/**
* Generate MakerKit-specific recommendations
*/
generateMakerKitRecommendations(constraint, result, originalData) {
const recommendations = [];
// Account type recommendations
if (originalData.hasOwnProperty('is_personal_account')) {
if (originalData.is_personal_account === undefined) {
recommendations.push('Consider explicitly setting is_personal_account for clarity');
}
}
// Slug recommendations
if (originalData.slug !== undefined) {
if (originalData.is_personal_account === false && !originalData.slug) {
recommendations.push('Team accounts benefit from meaningful slug generation');
}
}
// Organization recommendations
if (originalData.organization_id || originalData.organization_name) {
recommendations.push('Ensure organization-related constraints are properly validated');
}
return recommendations;
}
/**
* Update performance metrics
*/
updatePerformanceMetrics(testResult) {
const key = testResult.constraintName;
const existing = this.performanceMetrics.get(key);
if (existing) {
existing.totalExecutions++;
existing.averageExecutionTime = ((existing.averageExecutionTime * (existing.totalExecutions - 1)) + testResult.executionTime) / existing.totalExecutions;
existing.minExecutionTime = Math.min(existing.minExecutionTime, testResult.executionTime);
existing.maxExecutionTime = Math.max(existing.maxExecutionTime, testResult.executionTime);
existing.successRate = existing.successRate + (testResult.result.success ? 1 : 0) / existing.totalExecutions;
}
else {
this.performanceMetrics.set(key, {
constraintName: testResult.constraintName,
handlerName: testResult.handlerUsed,
averageExecutionTime: testResult.executionTime,
minExecutionTime: testResult.executionTime,
maxExecutionTime: testResult.executionTime,
totalExecutions: 1,
successRate: testResult.result.success ? 1 : 0
});
}
}
/**
* Update session summary
*/
updateSessionSummary(session, testResult) {
session.summary.totalTests++;
if (testResult.result.success) {
session.summary.successfulHandling++;
}
else {
session.summary.failedHandling++;
}
if (testResult.result.bypassRequired) {
session.summary.bypassRequired++;
}
// Update handler usage stats
const handler = testResult.handlerUsed;
session.summary.handlerUsageStats[handler] = (session.summary.handlerUsageStats[handler] || 0) + 1;
// Update issue distribution
testResult.issues.forEach(issue => {
const key = `${issue.severity}_${issue.type}`;
session.summary.issueDistribution[key] = (session.summary.issueDistribution[key] || 0) + 1;
});
// Update average execution time
const totalTime = session.results.reduce((sum, result) => sum + result.executionTime, 0);
session.summary.averageExecutionTime = totalTime / session.results.length;
}
/**
* Generate session-level recommendations
*/
generateSessionRecommendations(session) {
const summary = session.summary;
// Success rate recommendations
const successRate = summary.successfulHandling / summary.totalTests;
if (successRate < 0.8) {
summary.recommendations.push('Low constraint handling success rate - review handler implementations');
}
// Performance recommendations
if (summary.averageExecutionTime > 100) {
summary.recommendations.push('High average execution time - consider performance optimizations');
}
// Bypass recommendations
if (summary.bypassRequired > summary.totalTests * 0.1) {
summary.recommendations.push('High bypass rate - implement more specific constraint handlers');
}
// Issue-specific recommendations
const criticalIssues = Object.entries(summary.issueDistribution)
.filter(([key]) => key.startsWith('critical'))
.reduce((sum, [, count]) => sum + count, 0);
if (criticalIssues > 0) {
summary.recommendations.push('Critical issues detected - immediate attention required');
}
// Handler diversity recommendations
const uniqueHandlers = Object.keys(summary.handlerUsageStats).length;
if (uniqueHandlers < 3 && summary.totalConstraints > 5) {
summary.recommendations.push('Limited handler diversity - consider implementing more specific handlers');
}
}
/**
* Generate detailed debugging report
*/
generateDebuggingReport(sessionId, format = 'markdown') {
const session = this.activeSessions.get(sessionId);
if (!session) {
throw new Error(`Debugging session not found: ${sessionId}`);
}
switch (format) {
case 'json':
return JSON.stringify(session, null, 2);
case 'html':
return this.generateHtmlReport(session);
case 'markdown':
default:
return this.generateMarkdownReport(session);
}
}
/**
* Generate markdown debugging report
*/
generateMarkdownReport(session) {
const { summary } = session;
const successRate = ((summary.successfulHandling / summary.totalTests) * 100).toFixed(1);
return `# Constraint Debugging Report
## Session Information
- **Session ID**: ${session.sessionId}
- **Table**: ${session.tableName}
- **Start Time**: ${session.startTime.toISOString()}
- **Duration**: ${Date.now() - session.startTime.getTime()}ms
## Summary Statistics
- **Total Constraints**: ${summary.totalConstraints}
- **Total Tests**: ${summary.totalTests}
- **Success Rate**: ${successRate}%
- **Average Execution Time**: ${summary.averageExecutionTime.toFixed(2)}ms
- **Bypasses Required**: ${summary.bypassRequired}
## Handler Usage Statistics
${Object.entries(summary.handlerUsageStats)
.map(([handler, count]) => `- **${handler}**: ${count} uses`)
.join('\n')}
## Issue Distribution
${Object.entries(summary.issueDistribution)
.map(([type, count]) => `- **${type}**: ${count} occurrences`)
.join('\n')}
## Recommendations
${summary.recommendations.map(rec => `- ${rec}`).join('\n')}
## Performance Metrics
${Array.from(this.performanceMetrics.values())
.map(metric => `### ${metric.constraintName}
- **Handler**: ${metric.handlerName}
- **Average Time**: ${metric.averageExecutionTime.toFixed(2)}ms
- **Success Rate**: ${(metric.successRate * 100).toFixed(1)}%
- **Total Executions**: ${metric.totalExecutions}`)
.join('\n\n')}
## Detailed Test Results
${session.results
.filter(result => result.issues.length > 0 || result.recommendations.length > 0)
.map(result => `### ${result.constraintName} (Test ${result.testDataIndex})
- **Handler**: ${result.handlerUsed}
- **Execution Time**: ${result.executionTime}ms
- **Success**: ${result.result.success}
- **Fixes Applied**: ${result.result.appliedFixes.length}
${result.issues.length > 0 ? `**Issues:**
${result.issues.map(issue => `- [${issue.severity.toUpperCase()}] ${issue.message}`).join('\n')}` : ''}
${result.recommendations.length > 0 ? `**Recommendations:**
${result.recommendations.map(rec => `- ${rec}`).join('\n')}` : ''}`)
.join('\n\n')}
`;
}
/**
* Generate HTML debugging report
*/
generateHtmlReport(session) {
// Simplified HTML report generation
return `<!DOCTYPE html>
<html>
<head>
<title>Constraint Debugging Report - ${session.sessionId}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.summary { background: #f5f5f5; padding: 15px; border-radius: 5px; }
.issue-critical { color: #d32f2f; }
.issue-high { color: #f57c00; }
.issue-medium { color: #1976d2; }
.issue-low { color: #388e3c; }
</style>
</head>
<body>
<h1>Constraint Debugging Report</h1>
<div class="summary">
<h2>Session: ${session.sessionId}</h2>
<p><strong>Table:</strong> ${session.tableName}</p>
<p><strong>Total Tests:</strong> ${session.summary.totalTests}</p>
<p><strong>Success Rate:</strong> ${((session.summary.successfulHandling / session.summary.totalTests) * 100).toFixed(1)}%</p>
</div>
<!-- Additional HTML content would be generated here -->
</body>
</html>`;
}
/**
* Helper methods
*/
generateSessionId() {
return `debug_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
}
identifyHandlerUsed(constraint, result) {
// Try to identify which handler was used based on the result
if (result.bypassRequired) {
return 'none_bypass_required';
}
if (result.appliedFixes.length === 0) {
return 'generic_passthrough';
}
// Check for MakerKit-specific patterns
const fixReasons = result.appliedFixes.map(fix => fix.reason?.toLowerCase() || '');
if (fixReasons.some(reason => reason.includes('personal account'))) {
return 'makerkit_personal_account_slug';
}
if (fixReasons.some(reason => reason.includes('organization'))) {
return 'makerkit_organization_member';
}
if (fixReasons.some(reason => reason.includes('subscription'))) {
return 'makerkit_subscription_status';
}
return 'generic_handler';
}
inferConstraintType(constraint) {
if (constraint.checkClause)
return 'check';
if (constraint.referencedTable)
return 'foreign_key';
if (constraint.columns && constraint.columns.length > 1)
return 'unique';
if (constraint.notNull)
return 'not_null';
return 'check';
}
isMakerKitConstraint(constraint) {
const name = constraint.constraintName?.toLowerCase() || '';
return name.includes('account') ||
name.includes('organization') ||
name.includes('subscription') ||
name.includes('makerkit');
}
/**
* Clean up debugging session
*/
endDebuggingSession(sessionId) {
this.activeSessions.delete(sessionId);
logger_1.Logger.info(`Ended constraint debugging session: ${sessionId}`);
}
/**
* Get all active sessions
*/
getActiveSessions() {
return Array.from(this.activeSessions.keys());
}
}
exports.ConstraintDebuggingUtilities = ConstraintDebuggingUtilities;
exports.default = ConstraintDebuggingUtilities;
//# sourceMappingURL=constraint-debugging-utilities.js.map