supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
1,105 lines • 114 kB
JavaScript
"use strict";
/**
* MakerKit Framework Strategy
* Implements framework-specific seeding logic for MakerKit applications
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MakerKitStrategy = void 0;
const identity_manager_1 = require("../../../auth/identity-manager");
const development_webhook_manager_1 = require("../../../webhooks/development-webhook-manager");
const constraint_discovery_engine_1 = require("../../analysis/constraint-discovery-engine");
const constraint_registry_1 = require("../../analysis/constraint-registry");
const business_logic_analyzer_1 = require("../../analysis/business-logic-analyzer");
const rls_compliant_seeder_1 = require("../../../schema/rls-compliant-seeder");
const rls_compliance_engine_1 = require("../../analysis/rls-compliance-engine");
const rls_compliance_validator_1 = require("../../analysis/rls-compliance-validator");
const relationship_analyzer_1 = require("../../analysis/relationship-analyzer");
const junction_table_handler_1 = require("../../../schema/junction-table-handler");
const multi_tenant_manager_1 = require("../../../schema/multi-tenant-manager");
const storage_integration_manager_1 = require("../../generation/storage/storage-integration-manager");
const logger_1 = require("../../../core/utils/logger");
const detection_integration_1 = require("../../detection/detection-integration");
const auto_configurator_1 = require("../../detection/auto-configurator");
class MakerKitStrategy {
constructor() {
this.name = 'makerkit';
this.detectedFeatures = [];
}
async initialize(client) {
this.client = client;
this.constraintEngine = new constraint_discovery_engine_1.ConstraintDiscoveryEngine(client);
// Initialize identity manager for complete auth flows
this.identityManager = new identity_manager_1.IdentityManager(client);
// Initialize webhook manager for development webhook support
this.webhookManager = new development_webhook_manager_1.DevelopmentWebhookManager(client);
// Initialize enhanced RLS compliance validation system
this.rlsComplianceEngine = new rls_compliance_engine_1.RLSComplianceEngine(client);
this.rlsComplianceValidator = new rls_compliance_validator_1.RLSComplianceValidator(client);
// Initialize Smart Detection and Auto-Configuration (Task 2.3.2)
this.detectionEngine = new detection_integration_1.DetectionIntegrationEngine(client);
this.autoConfigurator = new auto_configurator_1.AutoConfigurator();
// Set default auth flow configuration for MakerKit
this.authFlowConfig = this.getDefaultAuthFlowConfig();
this.constraintRegistry = new constraint_registry_1.ConstraintRegistry({
enablePriorityHandling: true,
enableFallbackHandlers: true,
logHandlerSelection: true
});
// Initialize business logic analyzer
this.businessLogicAnalyzer = new business_logic_analyzer_1.BusinessLogicAnalyzer(client, {
frameworkHints: ['makerkit'],
expectedPatterns: ['auth_triggered', 'personal_account_constraint']
});
// Initialize RLS compliant seeder
this.rlsCompliantSeeder = new rls_compliant_seeder_1.RLSCompliantSeeder(client, undefined, {
enableRLSCompliance: true,
createUserContext: true,
useServiceRole: false // MakerKit prefers auth-based approach
});
// Initialize relationship analyzer with MakerKit-specific options
this.relationshipAnalyzer = new relationship_analyzer_1.RelationshipAnalyzer(client, {
schemas: ['public'],
detectJunctionTables: true,
analyzeTenantScoping: true, // Important for MakerKit multi-tenant
includeOptionalRelationships: true,
enableCaching: true,
generateRecommendations: true
});
// Initialize junction table handler for many-to-many relationships
this.junctionTableHandler = new junction_table_handler_1.JunctionTableHandler(client);
// Initialize multi-tenant manager with MakerKit-specific configuration
this.multiTenantManager = new multi_tenant_manager_1.MultiTenantManager(client, {
enableMultiTenant: true,
tenantColumn: 'account_id', // MakerKit uses account_id for tenant scoping
tenantScopeDetection: 'auto',
validationEnabled: true,
strictIsolation: true,
allowSharedResources: true,
dataGenerationOptions: {
generatePersonalAccounts: true,
generateTeamAccounts: true,
personalAccountRatio: 0.6, // 60% personal, 40% team
dataDistributionStrategy: 'realistic',
crossTenantDataAllowed: false,
sharedResourcesEnabled: true,
accountTypes: [
{
type: 'personal',
weight: 0.6,
settings: {
defaultPlan: 'free',
features: ['basic_features']
}
},
{
type: 'team',
weight: 0.4,
settings: {
minMembers: 2,
maxMembers: 10,
defaultPlan: 'pro',
features: ['team_features', 'collaboration']
}
}
],
minUsersPerTenant: 1,
maxUsersPerTenant: 5,
minProjectsPerTenant: 1,
maxProjectsPerTenant: 3,
allowCrossTenantRelationships: false,
sharedTables: ['plans', 'features'],
respectTenantPlans: true,
enforceTenantLimits: true
}
});
// Initialize storage integration manager with MakerKit-specific configuration
this.storageIntegrationManager = new storage_integration_manager_1.StorageIntegrationManager(client, {
bucketName: 'media',
domain: 'outdoor-adventure', // MakerKit default theme
categories: ['camping', 'hiking', 'outdoor-gear'],
imagesPerSetup: 3,
enableRealImages: false, // Default to mock for safety
imageService: 'mock',
maxFileSize: 5 * 1024 * 1024, // 5MB
allowedFileTypes: ['image/jpeg', 'image/png', 'image/webp'],
generateThumbnails: true,
respectRLS: true, // MakerKit uses RLS heavily
storageRootPath: 'supa-seed/makerkit'
});
// Register MakerKit-specific handlers
const handlers = this.getConstraintHandlers();
this.constraintRegistry.registerHandlers(handlers);
}
/**
* Perform smart detection and auto-configuration (Task 2.3.2)
*/
async performSmartDetectionAndConfiguration(baseConfiguration) {
if (!this.detectionEngine || !this.autoConfigurator) {
throw new Error('Detection engine or auto-configurator not initialized');
}
logger_1.Logger.info('🔍 Performing smart platform detection and auto-configuration...');
try {
// Step 1: Perform unified detection
const detectionStartTime = Date.now();
this.detectionResults = await this.detectionEngine.performUnifiedDetection({
enableCrossValidation: true,
enableConflictResolution: true,
enableCaching: true,
confidenceThreshold: 0.6
});
const detectionTime = Date.now() - detectionStartTime;
logger_1.Logger.success(`✅ Platform detection completed in ${detectionTime}ms`);
if (this.detectionResults?.architecture) {
logger_1.Logger.info(` Architecture: ${this.detectionResults.architecture.architectureType} (${(this.detectionResults.architecture.confidence * 100).toFixed(1)}% confidence)`);
}
if (this.detectionResults?.domain) {
logger_1.Logger.info(` Domain: ${this.detectionResults.domain.primaryDomain} (${(this.detectionResults.domain.confidence * 100).toFixed(1)}% confidence)`);
}
if (this.detectionResults?.integration) {
logger_1.Logger.info(` Overall Confidence: ${(this.detectionResults.integration.overallConfidence * 100).toFixed(1)}%`);
}
// Step 2: Generate auto-configuration based on detection
const configStartTime = Date.now();
this.autoConfiguration = await this.autoConfigurator.generateConfiguration(this.detectionResults, {
strategy: 'comprehensive',
baseConfiguration,
enableDomainExtensions: true,
enableArchitectureOptimizations: true,
confidenceThreshold: 0.6
});
const configTime = Date.now() - configStartTime;
logger_1.Logger.success(`✅ Auto-configuration generated in ${configTime}ms`);
if (this.autoConfiguration?.confidence !== undefined) {
logger_1.Logger.info(` Configuration Confidence: ${(this.autoConfiguration.confidence * 100).toFixed(1)}%`);
}
if (this.autoConfiguration?.generationMetrics?.templatesApplied !== undefined) {
logger_1.Logger.info(` Templates Applied: ${this.autoConfiguration.generationMetrics.templatesApplied}`);
}
// Log key configuration recommendations
if (this.autoConfiguration?.reasoning && this.autoConfiguration.reasoning.length > 0) {
logger_1.Logger.info('📋 Configuration Reasoning:');
this.autoConfiguration.reasoning.slice(0, 3).forEach(reason => {
logger_1.Logger.info(` • ${reason}`);
});
}
// Log warnings if any
if (this.autoConfiguration?.warnings && this.autoConfiguration.warnings.length > 0) {
logger_1.Logger.warn('⚠️ Configuration Warnings:');
this.autoConfiguration.warnings.forEach(warning => {
logger_1.Logger.warn(` • ${warning}`);
});
}
return this.autoConfiguration;
}
catch (error) {
logger_1.Logger.error('Smart detection and configuration failed:', error);
throw new Error(`Smart detection and configuration failed: ${error.message}`);
}
}
/**
* Get detection results (if available)
*/
getDetectionResults() {
return this.detectionResults;
}
/**
* Get auto-configuration results (if available)
*/
getAutoConfiguration() {
return this.autoConfiguration;
}
/**
* Apply auto-configuration to seeding process
*/
async applyAutoConfiguration(userConfiguration = {}) {
if (!this.autoConfiguration) {
logger_1.Logger.warn('No auto-configuration available - performing detection first');
await this.performSmartDetectionAndConfiguration(userConfiguration);
}
if (!this.autoConfiguration) {
throw new Error('Auto-configuration failed and is not available');
}
// Merge auto-configuration with user configuration (user config takes priority)
const mergedConfiguration = {
...this.autoConfiguration.configuration,
...userConfiguration
};
logger_1.Logger.info('🔧 Applied auto-configuration with user overrides');
// Log applied configuration summary
if (mergedConfiguration.userCount) {
logger_1.Logger.info(` User Count: ${mergedConfiguration.userCount}`);
}
if (mergedConfiguration.setupsPerUser) {
logger_1.Logger.info(` Setups per User: ${mergedConfiguration.setupsPerUser}`);
}
if (mergedConfiguration.domain) {
logger_1.Logger.info(` Domain: ${mergedConfiguration.domain}`);
}
if (mergedConfiguration.createTeamAccounts !== undefined) {
logger_1.Logger.info(` Team Accounts: ${mergedConfiguration.createTeamAccounts}`);
}
return mergedConfiguration;
}
/**
* Get platform-specific user archetypes based on detection
*/
getPlatformSpecificArchetypes() {
if (!this.detectionResults?.architecture || !this.detectionResults?.domain) {
return ['admin@test.com', 'user@test.com']; // Default archetypes
}
const { architectureType } = this.detectionResults.architecture;
const { primaryDomain } = this.detectionResults.domain;
// Generate platform-specific archetypes
const archetypes = [];
// Add architecture-specific archetypes
switch (architectureType) {
case 'individual':
archetypes.push('creator@test.com', 'explorer@test.com');
break;
case 'team':
archetypes.push('admin@test.com', 'team-lead@test.com', 'team-member@test.com');
break;
case 'hybrid':
archetypes.push('admin@test.com', 'creator@test.com', 'team-member@test.com');
break;
}
// Add domain-specific archetypes
switch (primaryDomain) {
case 'outdoor':
archetypes.push('gear-expert@wildernest.test', 'adventure-guide@wildernest.test');
break;
case 'saas':
archetypes.push('workspace-admin@saas.test', 'power-user@saas.test');
break;
case 'ecommerce':
archetypes.push('merchant@store.test', 'customer@store.test');
break;
case 'social':
archetypes.push('influencer@social.test', 'community-member@social.test');
break;
}
// Remove duplicates and return unique archetypes
return [...new Set(archetypes)];
}
/**
* Get optimized seeding parameters based on detection
*/
getOptimizedSeedingParameters() {
if (!this.detectionResults?.architecture || !this.detectionResults?.domain) {
return {
userCount: 5,
setupsPerUser: 2,
imagesPerSetup: 1,
enableRealImages: false
};
}
const { architectureType } = this.detectionResults.architecture;
const { primaryDomain } = this.detectionResults.domain;
let userCount = 5;
let setupsPerUser = 2;
let imagesPerSetup = 1;
let enableRealImages = false;
// Optimize based on architecture
switch (architectureType) {
case 'individual':
userCount = 4;
setupsPerUser = 3;
break;
case 'team':
userCount = 8;
setupsPerUser = 1;
break;
case 'hybrid':
userCount = 10;
setupsPerUser = 2;
break;
}
// Optimize based on domain
switch (primaryDomain) {
case 'outdoor':
imagesPerSetup = 3;
enableRealImages = true;
break;
case 'saas':
imagesPerSetup = 1;
enableRealImages = false;
break;
case 'ecommerce':
setupsPerUser = Math.max(setupsPerUser, 3); // Products
imagesPerSetup = 4;
enableRealImages = true;
break;
case 'social':
userCount = Math.max(userCount, 8); // More users for interactions
imagesPerSetup = 2;
enableRealImages = true;
break;
}
return {
userCount,
setupsPerUser,
imagesPerSetup,
enableRealImages
};
}
getPriority() {
return 100; // High priority for specific framework
}
async detect(schema) {
let confidence = 0;
const detectedFeatures = [];
const recommendations = [];
try {
// Check for MakerKit-specific functions
const makerKitFunctions = schema.functions.filter(fn => fn.name.includes('kit.setup_new_user') ||
fn.name.includes('handle_new_user') ||
fn.name.includes('create_profile_for_user'));
if (makerKitFunctions.length > 0) {
confidence += 0.4;
detectedFeatures.push('makerkit_user_functions');
logger_1.Logger.debug(`Found MakerKit functions: ${makerKitFunctions.map(f => f.name).join(', ')}`);
}
// Check for MakerKit-specific tables and columns
const accountsTable = schema.tables.find(t => t.name === 'accounts');
if (accountsTable) {
const hasPersonalAccountColumn = accountsTable.columns.some(c => c.name === 'is_personal_account');
const hasSlugColumn = accountsTable.columns.some(c => c.name === 'slug');
if (hasPersonalAccountColumn) {
confidence += 0.3;
detectedFeatures.push('personal_account_column');
}
if (hasSlugColumn) {
confidence += 0.1;
detectedFeatures.push('account_slug_column');
}
}
// Check for MakerKit constraint patterns
const personalAccountConstraint = schema.constraints.find(c => c.name.includes('accounts_slug_null_if_personal_account') ||
c.definition.includes('is_personal_account'));
if (personalAccountConstraint) {
confidence += 0.2;
detectedFeatures.push('personal_account_constraint');
logger_1.Logger.debug(`Found MakerKit constraint: ${personalAccountConstraint.name}`);
}
// Detect MakerKit version based on schema patterns
if (confidence > 0.5) {
this.version = this.detectMakerKitVersion(schema);
if (this.version) {
detectedFeatures.push(`makerkit_${this.version}`);
}
}
// Generate recommendations
if (confidence > 0.7) {
recommendations.push('Use auth.admin.createUser() for user creation');
recommendations.push('Ensure accounts have is_personal_account=true for profiles');
recommendations.push('Respect MakerKit trigger-based user creation flow');
}
else if (confidence > 0.3) {
recommendations.push('Partial MakerKit detection - verify schema compatibility');
recommendations.push('Consider manual framework override if using MakerKit');
}
this.detectedFeatures = detectedFeatures;
return {
framework: this.name,
version: this.version,
confidence,
detectedFeatures,
recommendations
};
}
catch (error) {
logger_1.Logger.warn(`MakerKit detection failed: ${error.message}`);
return {
framework: this.name,
confidence: 0,
detectedFeatures: [],
recommendations: ['Detection failed - check database permissions']
};
}
}
async createUser(data) {
// Convert UserData to CompleteUserData for new auth flow
const completeUserData = {
email: data.email,
password: data.password,
name: data.name,
username: data.username,
avatar: data.avatar,
bio: data.bio,
metadata: data.metadata,
identityProviders: ['email'], // Default to email provider
primaryProvider: 'email',
isPersonalAccount: true,
emailConfirmed: true
};
const result = await this.createCompleteUser(completeUserData);
if (!result.success || !result.authUser) {
throw new Error(`User creation failed: ${result.errors.join(', ')}`);
}
return {
id: result.authUser.id,
email: result.authUser.email,
name: data.name,
username: data.username,
avatar: data.avatar,
created_at: result.authUser.createdAt,
metadata: result.authUser.userMetadata
};
}
/**
* Create complete user with auth.users + auth.identities + accounts + profiles
* Implements FR-1.1: Complete authentication flow
*/
async createCompleteUser(data) {
if (!this.identityManager) {
throw new Error('Identity manager not initialized');
}
const result = {
success: false,
identities: [],
errors: [],
warnings: [],
recommendations: []
};
try {
logger_1.Logger.debug(`Creating complete MakerKit user: ${data.email}`);
// Step 1: Create auth.users record
const { data: authUser, error: authError } = await this.client.auth.admin.createUser({
email: data.email,
password: data.password || 'defaultPassword123!',
email_confirm: data.emailConfirmed ?? true,
user_metadata: {
name: data.name,
username: data.username,
avatar_url: data.avatar,
bio: data.bio,
...data.metadata
}
});
if (authError) {
result.errors.push(`Auth user creation failed: ${authError.message}`);
return result;
}
if (!authUser.user) {
result.errors.push('Auth user creation returned no user');
return result;
}
result.authUser = {
id: authUser.user.id,
email: authUser.user.email,
emailConfirmed: authUser.user.email_confirmed_at != null,
createdAt: authUser.user.created_at,
updatedAt: authUser.user.updated_at || authUser.user.created_at,
lastSignInAt: authUser.user.last_sign_in_at || undefined,
userMetadata: authUser.user.user_metadata,
appMetadata: authUser.user.app_metadata,
role: authUser.user.role,
aud: authUser.user.aud
};
// Step 2: Create auth.identities records
if (this.authFlowConfig.createIdentities && data.identityProviders?.length) {
logger_1.Logger.debug(`Creating identities for ${data.identityProviders.length} providers`);
for (const provider of data.identityProviders) {
const providerData = this.identityManager.generateOAuthProviderData(provider, data.email);
const identityResult = await this.identityManager.createIdentity({
userId: authUser.user.id,
provider: provider,
providerId: providerData.providerId,
email: data.email,
providerMetadata: providerData.metadata,
createdAt: authUser.user.created_at,
updatedAt: authUser.user.created_at
});
if (identityResult.success && identityResult.identity) {
result.identities.push({
id: identityResult.identity.id,
userId: identityResult.identity.user_id,
identityData: identityResult.identity.identity_data,
provider: identityResult.identity.provider,
lastSignInAt: identityResult.identity.last_sign_in_at,
createdAt: identityResult.identity.created_at,
updatedAt: identityResult.identity.updated_at
});
}
else {
result.warnings.push(`Identity creation failed for ${provider}: ${identityResult.error}`);
}
result.warnings.push(...identityResult.warnings);
}
}
// Step 2.5: Create MFA factors if enabled (FR-1.2: Add MFA Factor Support)
// TODO: MFA functionality temporarily disabled
// if (this.authFlowConfig.enableMFA && this.mfaManager) {
// Logger.debug('Creating MFA factors for complete auth flow');
//
// const mfaFactors = await this.createMFAFactorsForUser(authUser.user.id, data);
// if (mfaFactors.length > 0) {
// result.mfaFactors = mfaFactors;
// result.recommendations.push(`Created ${mfaFactors.length} MFA factor${mfaFactors.length !== 1 ? 's' : ''} for enhanced security`);
// } else {
// result.warnings.push('MFA enabled but no factors were created');
// }
// }
// Step 3: Wait for MakerKit triggers and handle account creation
if (this.authFlowConfig.useMakerKitTriggers) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Step 4: Verify/create account record
if (this.authFlowConfig.createAccountRecords) {
const accountResult = await this.ensureAccountExists(authUser.user.id, data);
if (accountResult) {
result.account = accountResult;
}
else {
result.warnings.push('Account creation failed or not completed');
}
}
// Step 5: Verify/create profile record
if (this.authFlowConfig.createProfileRecords) {
const profileResult = await this.ensureProfileExists(authUser.user.id, data);
if (profileResult) {
result.profile = profileResult;
}
else {
result.warnings.push('Profile creation failed or not completed');
}
}
// Step 6: Trigger development webhooks if enabled (FR-1.3)
if (this.authFlowConfig.setupDevelopmentWebhooks) {
await this.triggerUserCreatedWebhook(result.authUser);
}
// Add success recommendations
result.recommendations.push('Complete MakerKit auth flow successfully created', `Created ${result.identities.length} identity provider${result.identities.length !== 1 ? 's' : ''}`, 'User ready for MakerKit application testing');
if (this.authFlowConfig.setupDevelopmentWebhooks) {
result.recommendations.push('Development webhooks triggered for user creation');
}
result.success = true;
logger_1.Logger.success(`✅ Complete MakerKit user created: ${data.email}`);
return result;
}
catch (error) {
logger_1.Logger.error(`Complete user creation failed for ${data.email}:`, error);
result.errors.push(error.message);
return result;
}
}
async handleConstraints(table, data) {
const appliedFixes = [];
const warnings = [];
let processedData = { ...data };
try {
// Handle MakerKit-specific constraints
if (table === 'accounts') {
// Handle accounts_slug_null_if_personal_account_true constraint
if (processedData.is_personal_account === true && processedData.slug !== null) {
appliedFixes.push({
type: 'set_field',
field: 'slug',
oldValue: processedData.slug,
newValue: null,
reason: 'Personal accounts must have null slug (MakerKit constraint)',
confidence: 0.95
});
processedData.slug = null;
}
// Ensure personal accounts have is_personal_account set
if (processedData.is_personal_account === undefined) {
appliedFixes.push({
type: 'set_field',
field: 'is_personal_account',
oldValue: undefined,
newValue: true,
reason: 'Default to personal account for user profiles',
confidence: 0.9
});
processedData.is_personal_account = true;
processedData.slug = null; // Ensure slug is null for personal accounts
}
// Handle account_id for tenant-scoped data
if (!processedData.id && processedData.user_id) {
processedData.id = processedData.user_id;
}
}
// Handle profiles table
if (table === 'profiles') {
// Ensure profile has required fields
if (!processedData.name && processedData.display_name) {
appliedFixes.push({
type: 'set_field',
field: 'name',
oldValue: undefined,
newValue: processedData.display_name,
reason: 'Map display_name to name field',
confidence: 0.8
});
processedData.name = processedData.display_name;
}
}
// Handle tenant-scoped tables (require account_id)
const tenantScopedTables = ['setups', 'gear_items', 'trips', 'modifications', 'reviews', 'media_attachments'];
if (tenantScopedTables.includes(table) && !processedData.account_id) {
warnings.push('Tenant-scoped table requires account_id - ensure proper context');
}
return {
success: true,
originalData: data,
modifiedData: processedData,
appliedFixes,
warnings,
errors: [],
bypassRequired: false
};
}
catch (error) {
logger_1.Logger.warn(`Constraint handling failed for ${table}: ${error.message}`);
return {
success: false,
originalData: data,
modifiedData: processedData,
appliedFixes,
warnings: [...warnings, `Constraint handling error: ${error.message}`],
errors: [error.message],
bypassRequired: true
};
}
}
getRecommendations() {
const baseRecommendations = [
'Use auth.admin.createUser() for user creation to trigger MakerKit flows',
'Ensure personal accounts have slug=null to satisfy constraints',
'Let MakerKit triggers handle account and profile creation when possible',
'Respect tenant boundaries with proper account_id foreign keys'
];
if (this.version === 'v3') {
baseRecommendations.push('MakerKit v3 detected - use latest API patterns');
}
if (this.detectedFeatures.includes('personal_account_constraint')) {
baseRecommendations.push('Personal account constraint detected - auto-fix enabled');
}
return baseRecommendations;
}
supportsFeature(feature) {
const supportedFeatures = [
'auth_trigger_user_creation',
'constraint_auto_fix',
'personal_account_handling',
'tenant_scoped_data',
'rls_compliance',
'business_logic_respect',
'constraint_discovery',
'framework_specific_handlers',
'mfa_factor_support', // FR-1.2: Add MFA Factor Support
'development_webhook_setup' // FR-1.3: Development Webhook Setup
];
return supportedFeatures.includes(feature);
}
/**
* Configure MFA settings for the strategy
* Implements FR-1.2: Add MFA Factor Support
*/
configureMFA(enableMFA, options) {
this.authFlowConfig.enableMFA = enableMFA;
if (enableMFA) {
logger_1.Logger.info('✅ MFA support enabled for MakerKit strategy');
if (options?.enforceForRoles?.length) {
logger_1.Logger.info(`MFA will be enforced for roles: ${options.enforceForRoles.join(', ')}`);
}
if (options?.defaultSecurityLevel) {
logger_1.Logger.info(`Default MFA security level: ${options.defaultSecurityLevel}`);
}
}
else {
logger_1.Logger.info('MFA support disabled');
}
}
/**
* Get MFA validation result for the platform
* TODO: MFA functionality temporarily disabled
*/
async validateMFASupport() {
// TODO: MFA functionality temporarily disabled
return {
supported: false,
tableExists: false,
hasPermissions: false,
errors: ['MFA functionality temporarily disabled'],
warnings: []
};
}
/**
* Configure development webhooks for the strategy
* Implements FR-1.3: Development Webhook Setup
*/
async configureWebhooks(config) {
if (!this.webhookManager) {
throw new Error('Webhook manager not initialized');
}
await this.webhookManager.configure(config);
this.authFlowConfig.setupDevelopmentWebhooks = config.enabled;
logger_1.Logger.info(`✅ Development webhooks ${config.enabled ? 'enabled' : 'disabled'} for MakerKit strategy`);
}
/**
* Setup development webhook endpoints automatically
*/
async setupDevelopmentWebhooks() {
if (!this.webhookManager) {
throw new Error('Webhook manager not initialized');
}
if (!this.authFlowConfig.setupDevelopmentWebhooks) {
logger_1.Logger.debug('Development webhooks are disabled');
return {
success: false,
endpoints: [],
errors: ['Development webhooks are disabled in auth flow configuration']
};
}
logger_1.Logger.info('🔗 Setting up MakerKit development webhooks...');
const result = await this.webhookManager.setupDevelopmentEndpoints();
if (result.success) {
logger_1.Logger.success(`✅ Setup ${result.endpoints.length} development webhook endpoints`);
// Trigger webhooks for newly created users if enabled
if (result.endpoints.some(ep => ep.events.includes('user.created'))) {
logger_1.Logger.info('💡 Webhook endpoints ready for user creation events');
}
}
else {
logger_1.Logger.error('❌ Failed to setup development webhooks:', result.errors);
}
return result;
}
/**
* Generate platform-specific webhook configuration
*/
generatePlatformWebhookConfig(architecture = 'individual', domain = 'generic') {
if (!this.webhookManager) {
logger_1.Logger.warn('Webhook manager not initialized');
return null;
}
const config = this.webhookManager.generatePlatformWebhookConfig(architecture, domain);
logger_1.Logger.info(`📋 Generated webhook configuration for ${architecture} ${domain} platform`);
return config;
}
/**
* Validate webhook support for the platform
*/
async validateWebhookSupport() {
if (!this.webhookManager) {
return {
supported: false,
configured: false,
errors: ['Webhook manager not initialized'],
warnings: []
};
}
try {
const config = this.webhookManager.getConfiguration();
const endpoints = await this.webhookManager.listEndpoints();
return {
supported: this.authFlowConfig.setupDevelopmentWebhooks,
configured: config !== null,
errors: [],
warnings: endpoints.length === 0 ? ['No webhook endpoints configured'] : []
};
}
catch (error) {
return {
supported: false,
configured: false,
errors: [`Webhook validation failed: ${error.message}`],
warnings: []
};
}
}
/**
* Trigger webhook for user creation (integrates with createCompleteUser)
*/
async triggerUserCreatedWebhook(user) {
if (!this.webhookManager || !this.authFlowConfig.setupDevelopmentWebhooks) {
return;
}
try {
const payload = {
type: 'user.created',
user: {
id: user.id,
email: user.email,
created_at: user.createdAt,
updated_at: user.updatedAt,
user_metadata: user.userMetadata || {},
app_metadata: user.appMetadata || {}
}
};
const results = await this.webhookManager.triggerWebhook('user.created', payload);
if (results.length > 0) {
const successful = results.filter(r => r.success).length;
logger_1.Logger.info(`📡 Triggered ${successful}/${results.length} user creation webhooks`);
}
}
catch (error) {
logger_1.Logger.warn(`Failed to trigger user creation webhook: ${error.message}`);
}
}
/**
* Discover constraints using MakerKit-aware analysis
*/
async discoverConstraints(tableNames) {
if (!this.constraintEngine) {
throw new Error('Constraint engine not initialized');
}
try {
logger_1.Logger.debug('Discovering constraints with MakerKit strategy');
// Use MakerKit-specific table focus if no tables specified
const targetTables = tableNames || [
'accounts', 'profiles', 'subscriptions',
'organizations', 'organization_members', 'invitations'
];
const discoveryResult = await this.constraintEngine.discoverConstraints(targetTables);
// Convert discovery engine tables to constraint types format with MakerKit-specific enhancements
const enhancedTables = discoveryResult.tables.map(table => {
let confidence = discoveryResult.confidence;
// Add MakerKit-specific confidence boost for accounts table
if (table.tableName === 'accounts' && table.constraints.length > 0) {
confidence = Math.min(confidence + 0.2, 1.0);
}
return {
table: table.tableName,
schema: 'public',
checkConstraints: [],
foreignKeyConstraints: [],
uniqueConstraints: [],
primaryKeyConstraints: [],
notNullConstraints: [],
confidence,
discoveryTimestamp: new Date().toISOString()
};
});
return {
success: true,
tables: enhancedTables,
totalConstraints: discoveryResult.businessRules?.length || 0,
confidence: discoveryResult.confidence,
businessRules: discoveryResult.businessRules || [],
errors: [],
warnings: [],
recommendations: [
...this.getRecommendations(),
'Enable constraint-aware data generation for better reliability'
]
};
}
catch (error) {
logger_1.Logger.error('MakerKit constraint discovery failed:', error);
return {
success: false,
tables: [],
totalConstraints: 0,
confidence: 0,
businessRules: [],
errors: [error.message],
warnings: [],
recommendations: ['Review constraint discovery configuration']
};
}
}
/**
* Get MakerKit-specific constraint handlers
*/
getConstraintHandlers() {
return [
{
id: 'makerkit_personal_account_slug',
type: 'check',
priority: 100,
description: 'Handles MakerKit personal account slug constraint',
canHandle: (constraint) => {
return constraint.constraintName?.toLowerCase().includes('accounts_slug_null_if_personal') ||
(constraint.checkClause?.toLowerCase().includes('is_personal_account') &&
constraint.checkClause?.toLowerCase().includes('slug'));
},
handle: (constraint, data) => {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
// If is_personal_account is true, slug must be null
if (data.is_personal_account === true && data.slug !== null) {
result.modifiedData.slug = null;
result.appliedFixes.push({
type: 'set_field',
field: 'slug',
oldValue: data.slug,
newValue: null,
reason: 'Personal accounts must have null slug (MakerKit constraint)',
confidence: 0.95
});
}
// Default to personal account if not specified
if (data.is_personal_account === undefined) {
result.modifiedData.is_personal_account = true;
result.modifiedData.slug = null;
result.appliedFixes.push({
type: 'set_field',
field: 'is_personal_account',
oldValue: undefined,
newValue: true,
reason: 'Default to personal account for profile compatibility',
confidence: 0.9
});
}
return result;
}
},
{
id: 'makerkit_organization_member_unique',
type: 'unique',
priority: 90,
description: 'Handles MakerKit organization member uniqueness',
canHandle: (constraint) => {
return constraint.constraintName?.toLowerCase().includes('organization_member') ||
(constraint.columns?.includes('organization_id') && constraint.columns?.includes('user_id'));
},
handle: (constraint, data) => {
return {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: data.organization_id && data.user_id ? [] :
['Organization member requires both organization_id and user_id'],
errors: [],
bypassRequired: false
};
}
}
];
}
/**
* Apply constraint fixes using MakerKit-aware logic
*/
async applyConstraintFixes(table, data, constraints) {
if (!this.constraintRegistry) {
throw new Error('Constraint registry not initialized');
}
try {
logger_1.Logger.debug(`Applying MakerKit constraint fixes for table: ${table}`);
const result = this.constraintRegistry.handleTableConstraints(constraints, data);
// Add MakerKit-specific enhancements
if (table === 'accounts' && !result.appliedFixes.some(f => f.field === 'is_personal_account')) {
// Ensure personal account defaults for MakerKit
if (data.is_personal_account === undefined) {
result.modifiedData.is_personal_account = true;
result.modifiedData.slug = null;
result.appliedFixes.push({
type: 'set_field',
field: 'is_personal_account',
oldValue: undefined,
newValue: true,
reason: 'MakerKit default: personal account for user profiles',
confidence: 0.9
});
}
}
return result;
}
catch (error) {
logger_1.Logger.error('MakerKit constraint fix application failed:', error);
return {
success: false,
originalData: data,
modifiedData: data,
appliedFixes: [],
warnings: [],
errors: [error.message],
bypassRequired: true
};
}
}
/**
* Analyze business logic patterns for MakerKit
*/
async analyzeBusinessLogic() {
if (!this.businessLogicAnalyzer) {
throw new Error('Business logic analyzer not initialized');
}
try {
logger_1.Logger.debug('Analyzing MakerKit business logic patterns');
const analysis = await this.businessLogicAnalyzer.analyzeBusinessLogic();
// Add MakerKit-specific enhancements to the analysis
if (analysis.success) {
analysis.framework = 'makerkit';
// Boost confidence if MakerKit patterns are detected
if (analysis.triggerAnalysis.userCreationFlow?.usesAuthTriggers) {
analysis.confidence = Math.min(analysis.confidence + 0.1, 1.0);
}
// Add MakerKit-specific recommendations
const makerKitRecommendations = [
'Use auth.admin.createUser() for proper MakerKit workflow',
'Ensure is_personal_account=true for personal profiles',
'Let triggers handle account and profile creation'
];
analysis.warnings.push(...makerKitRecommendations);
}
return analysis;
}
catch (error) {
logger_1.Logger.error('MakerKit business logic analysis failed:', error);
throw error;
}
}
/**
* Seed data with RLS compliance for MakerKit
*/
async seedWithRLSCompliance(table, data, userContext) {
if (!this.rlsCompliantSeeder) {
throw new Error('RLS compliant seeder not initialized');
}
try {
logger_1.Logger.debug(`MakerKit RLS-compliant seeding for table: ${table}`);
// For MakerKit, we prefer auth-triggered workflows
const result = await this.rlsCompliantSeeder.seedWithRLSCompliance(table, data, userContext);
// Add MakerKit-specific context to the result
if (result.success && result.userContext) {
result.warnings = result.warnings || [];
result.warnings.push('Used MakerKit auth-triggered workflow');
}
return result;
}
c