UNPKG

supa-seed

Version:

A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support

632 lines 26.5 kB
"use strict"; /** * MakerKit-Specific Constraint Handlers for Epic 1: Universal MakerKit Core System * Advanced constraint resolution strategies for complex MakerKit relationship patterns * Part of Task 1.5.2: Create MakerKit-specific constraint handlers */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MAKERKIT_CONSTRAINT_HANDLERS = exports.MakerKitCascadeConstraintHandler = exports.MakerKitBusinessRuleConstraintHandler = exports.MakerKitHierarchicalConstraintHandler = exports.MakerKitConditionalForeignKeyHandler = exports.MakerKitCrossTableConstraintHandler = void 0; exports.registerMakerKitConstraintHandlers = registerMakerKitConstraintHandlers; const logger_1 = require("../core/utils/logger"); /** * Advanced MakerKit Cross-Table Constraint Handler * Handles complex multi-table relationship constraints */ class MakerKitCrossTableConstraintHandler { constructor() { this.id = 'makerkit_cross_table'; this.type = 'check'; this.priority = 120; this.description = 'Handles MakerKit cross-table constraint relationships'; } canHandle(constraint, data) { return (constraint.constraintName?.toLowerCase().includes('cross_table') || constraint.constraintName?.toLowerCase().includes('dependent') || constraint.checkClause?.toLowerCase().includes('exists') || constraint.checkClause?.toLowerCase().includes('select')); } handle(constraint, data) { const result = { success: true, originalData: { ...data }, modifiedData: { ...data }, appliedFixes: [], warnings: [], errors: [], bypassRequired: false }; // Handle account-subscription dependency constraints if (this.isAccountSubscriptionConstraint(constraint)) { this.handleAccountSubscriptionDependency(constraint, data, result); } // Handle organization-member cascade constraints if (this.isOrganizationMemberConstraint(constraint)) { this.handleOrganizationMemberCascade(constraint, data, result); } // Handle user-profile consistency constraints if (this.isUserProfileConstraint(constraint)) { this.handleUserProfileConsistency(constraint, data, result); } return result; } isAccountSubscriptionConstraint(constraint) { const clause = constraint.checkClause?.toLowerCase() || ''; return (clause.includes('subscription') && (clause.includes('account') || clause.includes('organization'))); } isOrganizationMemberConstraint(constraint) { const clause = constraint.checkClause?.toLowerCase() || ''; return (clause.includes('organization') && clause.includes('member') && clause.includes('exists')); } isUserProfileConstraint(constraint) { const clause = constraint.checkClause?.toLowerCase() || ''; return (clause.includes('user') && clause.includes('profile') && clause.includes('auth')); } handleAccountSubscriptionDependency(constraint, data, result) { // Ensure subscription has valid account reference if (data.account_id && !data.subscription_id) { result.warnings.push('Account created without subscription - may require subscription setup'); } // Handle subscription status constraints with account type if (data.is_personal_account === true && data.subscription_status === 'team') { result.modifiedData.subscription_status = 'individual'; result.appliedFixes.push({ type: 'set_field', field: 'subscription_status', oldValue: data.subscription_status, newValue: 'individual', reason: 'Personal accounts cannot have team subscription status', confidence: 0.95 }); } } handleOrganizationMemberCascade(constraint, data, result) { // Handle organization creation with automatic owner membership if (data.organization_id && data.created_by && !data.member_id) { result.warnings.push('Organization created without owner membership - requires member record creation'); // Add dependency for member creation result.appliedFixes.push({ type: 'add_dependency', field: 'organization_members', oldValue: null, newValue: { organization_id: data.organization_id, user_id: data.created_by, role: 'owner', is_owner: true }, reason: 'Create owner membership for new organization', confidence: 0.9 }); } // Handle member role changes with organization constraints if (data.role === 'owner' && data.organization_members_count === 0) { result.warnings.push('Cannot assign owner role to organization with no members'); } } handleUserProfileConsistency(constraint, data, result) { // Ensure auth.users and profiles consistency if (data.user_id && !data.profile_id) { result.warnings.push('User created without profile - profile creation may be required'); } // Handle email consistency between auth and profile if (data.auth_email && data.profile_email && data.auth_email !== data.profile_email) { result.modifiedData.profile_email = data.auth_email; result.appliedFixes.push({ type: 'set_field', field: 'profile_email', oldValue: data.profile_email, newValue: data.auth_email, reason: 'Sync profile email with auth email for consistency', confidence: 0.9 }); } } } exports.MakerKitCrossTableConstraintHandler = MakerKitCrossTableConstraintHandler; /** * MakerKit Conditional Foreign Key Handler * Handles nullable foreign keys with business logic conditions */ class MakerKitConditionalForeignKeyHandler { constructor() { this.id = 'makerkit_conditional_fk'; this.type = 'foreign_key'; this.priority = 110; this.description = 'Handles MakerKit conditional foreign key constraints'; } canHandle(constraint, data) { return (constraint.constraintName?.toLowerCase().includes('conditional') || constraint.constraintName?.toLowerCase().includes('nullable') || this.isConditionalForeignKey(constraint)); } handle(constraint, data) { const result = { success: true, originalData: { ...data }, modifiedData: { ...data }, appliedFixes: [], warnings: [], errors: [], bypassRequired: false }; // Handle organization_id that's nullable for personal accounts if (this.isOrganizationForeignKey(constraint)) { this.handleOrganizationForeignKey(constraint, data, result); } // Handle parent_id for hierarchical structures if (this.isHierarchicalForeignKey(constraint)) { this.handleHierarchicalForeignKey(constraint, data, result); } // Handle subscription_id for conditional billing if (this.isSubscriptionForeignKey(constraint)) { this.handleSubscriptionForeignKey(constraint, data, result); } return result; } isConditionalForeignKey(constraint) { const conditionalPatterns = [ 'organization_id', 'parent_id', 'subscription_id', 'billing_customer_id', 'team_id' ]; return conditionalPatterns.some(pattern => constraint.columnName?.toLowerCase().includes(pattern)); } isOrganizationForeignKey(constraint) { return constraint.columnName?.toLowerCase().includes('organization_id') || false; } isHierarchicalForeignKey(constraint) { return constraint.columnName?.toLowerCase().includes('parent_id') || false; } isSubscriptionForeignKey(constraint) { return (constraint.columnName?.toLowerCase().includes('subscription_id') || constraint.columnName?.toLowerCase().includes('billing_customer_id')) || false; } handleOrganizationForeignKey(constraint, data, result) { // Personal accounts should not have organization_id if (data.is_personal_account === true && data.organization_id) { result.modifiedData.organization_id = null; result.appliedFixes.push({ type: 'set_field', field: 'organization_id', oldValue: data.organization_id, newValue: null, reason: 'Personal accounts should not have organization_id', confidence: 0.95 }); } // Team accounts must have organization_id if (data.is_personal_account === false && !data.organization_id) { result.warnings.push('Team account requires organization_id - organization creation may be needed'); // Could suggest organization creation result.appliedFixes.push({ type: 'add_dependency', field: 'organizations', oldValue: null, newValue: { name: data.organization_name || `${data.name || 'User'}'s Organization`, slug: this.generateOrganizationSlug(data), created_by: data.user_id || data.id }, reason: 'Create organization for team account', confidence: 0.8 }); } } handleHierarchicalForeignKey(constraint, data, result) { // Prevent self-referencing parent relationships if (data.parent_id === data.id) { result.modifiedData.parent_id = null; result.appliedFixes.push({ type: 'set_field', field: 'parent_id', oldValue: data.parent_id, newValue: null, reason: 'Prevent self-referencing parent relationship', confidence: 0.95 }); } // Validate hierarchical depth constraints if (data.parent_id && data.hierarchy_level > 5) { result.warnings.push('Deep hierarchy detected - may cause performance issues'); } } handleSubscriptionForeignKey(constraint, data, result) { // Free tier accounts may not have subscription_id if (data.plan_type === 'free' && data.subscription_id) { result.modifiedData.subscription_id = null; result.appliedFixes.push({ type: 'set_field', field: 'subscription_id', oldValue: data.subscription_id, newValue: null, reason: 'Free tier accounts should not have subscription_id', confidence: 0.9 }); } // Paid accounts should have subscription_id if (data.plan_type && data.plan_type !== 'free' && !data.subscription_id) { result.warnings.push('Paid account requires subscription_id - subscription creation may be needed'); } } generateOrganizationSlug(data) { const baseName = data.organization_name || data.name || 'organization'; return baseName .toLowerCase() .trim() .replace(/[^a-z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, '') .substring(0, 50); } } exports.MakerKitConditionalForeignKeyHandler = MakerKitConditionalForeignKeyHandler; /** * MakerKit Hierarchical Constraint Handler * Handles parent-child relationship constraints and circular dependency prevention */ class MakerKitHierarchicalConstraintHandler { constructor() { this.id = 'makerkit_hierarchical'; this.type = 'check'; this.priority = 105; this.description = 'Handles MakerKit hierarchical relationship constraints'; } canHandle(constraint, data) { return (constraint.constraintName?.toLowerCase().includes('hierarchical') || constraint.constraintName?.toLowerCase().includes('parent') || constraint.constraintName?.toLowerCase().includes('circular') || constraint.checkClause?.toLowerCase().includes('parent_id') || constraint.checkClause?.toLowerCase().includes('hierarchy')); } handle(constraint, data) { const result = { success: true, originalData: { ...data }, modifiedData: { ...data }, appliedFixes: [], warnings: [], errors: [], bypassRequired: false }; // Prevent circular references if (this.isCircularReference(data)) { this.handleCircularReference(data, result); } // Validate hierarchy depth if (this.isDeepHierarchy(data)) { this.handleDeepHierarchy(data, result); } // Handle orphaned records if (this.isOrphanedRecord(data)) { this.handleOrphanedRecord(data, result); } return result; } isCircularReference(data) { return data.parent_id === data.id || (data.parent_id && data.children?.some((child) => child.id === data.parent_id)); } isDeepHierarchy(data) { return (data.hierarchy_level || 0) > 10; } isOrphanedRecord(data) { return data.parent_id && !data.parent_exists && data.level > 0; } handleCircularReference(data, result) { result.modifiedData.parent_id = null; result.appliedFixes.push({ type: 'set_field', field: 'parent_id', oldValue: data.parent_id, newValue: null, reason: 'Removed circular reference in hierarchy', confidence: 0.95 }); result.warnings.push('Circular reference detected and resolved by removing parent relationship'); } handleDeepHierarchy(data, result) { result.warnings.push(`Deep hierarchy detected (level ${data.hierarchy_level}) - consider flattening structure`); // Optionally limit hierarchy depth if (data.hierarchy_level > 15) { result.modifiedData.parent_id = null; result.appliedFixes.push({ type: 'set_field', field: 'parent_id', oldValue: data.parent_id, newValue: null, reason: 'Hierarchy too deep, moved to root level', confidence: 0.7 }); } } handleOrphanedRecord(data, result) { result.modifiedData.parent_id = null; result.modifiedData.hierarchy_level = 0; result.appliedFixes.push({ type: 'set_field', field: 'parent_id', oldValue: data.parent_id, newValue: null, reason: 'Removed reference to non-existent parent', confidence: 0.9 }); result.appliedFixes.push({ type: 'set_field', field: 'hierarchy_level', oldValue: data.hierarchy_level, newValue: 0, reason: 'Reset hierarchy level for orphaned record', confidence: 0.9 }); } } exports.MakerKitHierarchicalConstraintHandler = MakerKitHierarchicalConstraintHandler; /** * MakerKit Business Rule Constraint Handler * Handles complex business logic constraints that span multiple conditions */ class MakerKitBusinessRuleConstraintHandler { constructor() { this.id = 'makerkit_business_rule'; this.type = 'check'; this.priority = 115; this.description = 'Handles MakerKit business rule constraints'; } canHandle(constraint, data) { return (constraint.constraintName?.toLowerCase().includes('business_rule') || constraint.constraintName?.toLowerCase().includes('workflow') || this.isComplexBusinessRule(constraint)); } handle(constraint, data) { const result = { success: true, originalData: { ...data }, modifiedData: { ...data }, appliedFixes: [], warnings: [], errors: [], bypassRequired: false }; // Handle subscription-billing business rules if (this.isSubscriptionBillingRule(constraint)) { this.handleSubscriptionBillingRule(constraint, data, result); } // Handle organization-membership business rules if (this.isOrganizationMembershipRule(constraint)) { this.handleOrganizationMembershipRule(constraint, data, result); } // Handle user-permission business rules if (this.isUserPermissionRule(constraint)) { this.handleUserPermissionRule(constraint, data, result); } return result; } isComplexBusinessRule(constraint) { const clause = constraint.checkClause?.toLowerCase() || ''; return (clause.includes('case when') || clause.includes('and') && clause.includes('or') || clause.split('and').length > 2 || clause.includes('not exists')); } isSubscriptionBillingRule(constraint) { const clause = constraint.checkClause?.toLowerCase() || ''; return clause.includes('subscription') && clause.includes('billing'); } isOrganizationMembershipRule(constraint) { const clause = constraint.checkClause?.toLowerCase() || ''; return clause.includes('organization') && clause.includes('member'); } isUserPermissionRule(constraint) { const clause = constraint.checkClause?.toLowerCase() || ''; return clause.includes('permission') || clause.includes('role') && clause.includes('access'); } handleSubscriptionBillingRule(constraint, data, result) { // Business rule: Active subscription must have valid billing if (data.subscription_status === 'active' && !data.stripe_customer_id) { result.warnings.push('Active subscription requires valid billing setup'); } // Business rule: Canceled subscription should not have active billing if (data.subscription_status === 'canceled' && data.billing_status === 'active') { result.modifiedData.billing_status = 'canceled'; result.appliedFixes.push({ type: 'set_field', field: 'billing_status', oldValue: data.billing_status, newValue: 'canceled', reason: 'Sync billing status with canceled subscription', confidence: 0.9 }); } } handleOrganizationMembershipRule(constraint, data, result) { // Business rule: Organization must have exactly one owner if (data.role === 'owner' && data.existing_owners_count > 0) { result.modifiedData.role = 'admin'; result.appliedFixes.push({ type: 'set_field', field: 'role', oldValue: 'owner', newValue: 'admin', reason: 'Organization already has an owner, assigning admin role', confidence: 0.85 }); } // Business rule: Cannot remove last member from organization if (data.action === 'remove' && data.total_members === 1) { result.errors.push('Cannot remove last member from organization'); result.success = false; } } handleUserPermissionRule(constraint, data, result) { // Business rule: Admin role requires specific permissions if (data.role === 'admin' && !data.can_manage_members) { result.modifiedData.can_manage_members = true; result.appliedFixes.push({ type: 'set_field', field: 'can_manage_members', oldValue: false, newValue: true, reason: 'Admin role requires member management permission', confidence: 0.9 }); } // Business rule: Viewer role should have minimal permissions if (data.role === 'viewer' && (data.can_create || data.can_delete)) { result.modifiedData.can_create = false; result.modifiedData.can_delete = false; result.appliedFixes.push({ type: 'set_field', field: 'can_create', oldValue: data.can_create, newValue: false, reason: 'Viewer role should not have create permissions', confidence: 0.95 }); } } } exports.MakerKitBusinessRuleConstraintHandler = MakerKitBusinessRuleConstraintHandler; /** * MakerKit Cascade Constraint Handler * Handles cascade operations and dependent data creation/updates */ class MakerKitCascadeConstraintHandler { constructor() { this.id = 'makerkit_cascade'; this.type = 'foreign_key'; this.priority = 100; this.description = 'Handles MakerKit cascade constraint operations'; } canHandle(constraint, data) { return (constraint.onDelete === 'CASCADE' || constraint.onUpdate === 'CASCADE' || constraint.constraintName?.toLowerCase().includes('cascade')); } handle(constraint, data) { const result = { success: true, originalData: { ...data }, modifiedData: { ...data }, appliedFixes: [], warnings: [], errors: [], bypassRequired: false }; // Handle organization deletion cascades if (this.isOrganizationCascade(constraint, data)) { this.handleOrganizationCascade(constraint, data, result); } // Handle user deletion cascades if (this.isUserCascade(constraint, data)) { this.handleUserCascade(constraint, data, result); } // Handle subscription cancellation cascades if (this.isSubscriptionCascade(constraint, data)) { this.handleSubscriptionCascade(constraint, data, result); } return result; } isOrganizationCascade(constraint, data) { return constraint.referencedTable?.toLowerCase().includes('organization') || constraint.columnName?.toLowerCase().includes('organization_id') || false; } isUserCascade(constraint, data) { return constraint.referencedTable?.toLowerCase().includes('user') || constraint.columnName?.toLowerCase().includes('user_id') || false; } isSubscriptionCascade(constraint, data) { return constraint.referencedTable?.toLowerCase().includes('subscription') || constraint.columnName?.toLowerCase().includes('subscription_id') || false; } handleOrganizationCascade(constraint, data, result) { if (data.action === 'delete' && data.has_members) { result.warnings.push('Deleting organization will cascade to remove all members'); // Add cascade dependency information result.appliedFixes.push({ type: 'add_dependency', field: 'cascade_operations', oldValue: null, newValue: { operation: 'delete_members', table: 'organization_members', condition: `organization_id = ${data.organization_id}` }, reason: 'Cascade delete organization members', confidence: 0.9 }); } } handleUserCascade(constraint, data, result) { if (data.action === 'delete' && data.has_profiles) { result.warnings.push('Deleting user will cascade to remove profile and related data'); } // Handle user update cascades if (data.action === 'update' && data.email_changed) { result.appliedFixes.push({ type: 'add_dependency', field: 'cascade_operations', oldValue: null, newValue: { operation: 'update_profiles', table: 'profiles', condition: `user_id = ${data.user_id}`, updates: { email: data.new_email } }, reason: 'Cascade email update to profile', confidence: 0.95 }); } } handleSubscriptionCascade(constraint, data, result) { if (data.subscription_status === 'canceled') { result.warnings.push('Subscription cancellation may affect related services'); // Add cascade operations for subscription cancellation result.appliedFixes.push({ type: 'add_dependency', field: 'cascade_operations', oldValue: null, newValue: { operation: 'update_features', table: 'user_features', condition: `subscription_id = ${data.subscription_id}`, updates: { enabled: false } }, reason: 'Disable features for canceled subscription', confidence: 0.9 }); } } } exports.MakerKitCascadeConstraintHandler = MakerKitCascadeConstraintHandler; /** * Registry of all MakerKit-specific constraint handlers */ exports.MAKERKIT_CONSTRAINT_HANDLERS = [ MakerKitCrossTableConstraintHandler, MakerKitConditionalForeignKeyHandler, MakerKitHierarchicalConstraintHandler, MakerKitBusinessRuleConstraintHandler, MakerKitCascadeConstraintHandler ]; /** * Initialize and register all MakerKit-specific constraint handlers */ function registerMakerKitConstraintHandlers(handlers) { exports.MAKERKIT_CONSTRAINT_HANDLERS.forEach(HandlerClass => { const handler = new HandlerClass(); handlers.registerHandler(handler); logger_1.Logger.debug(`Registered MakerKit constraint handler: ${handler.id}`); }); } //# sourceMappingURL=makerkit-constraint-handlers.js.map