supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
833 lines • 31.2 kB
JavaScript
"use strict";
/**
* Constraint Handlers System
* Implements handlers for different types of PostgreSQL constraints
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConstraintHandlers = void 0;
const logger_1 = require("../../core/utils/logger");
const makerkit_constraint_handlers_1 = require("../../schema/makerkit-constraint-handlers");
class ConstraintHandlers {
/**
* Initialize default constraint handlers
*/
static initializeDefaultHandlers() {
// Register advanced MakerKit-specific constraint handlers
(0, makerkit_constraint_handlers_1.registerMakerKitConstraintHandlers)(this);
// Enhanced MakerKit-specific handlers
this.registerHandler(new MakerKitPersonalAccountSlugHandler());
this.registerHandler(new MakerKitTeamAccountSlugHandler());
this.registerHandler(new MakerKitOrganizationMemberHandler());
this.registerHandler(new MakerKitSubscriptionStatusHandler());
this.registerHandler(new MakerKitAccountTypeHandler());
this.registerHandler(new MakerKitUserRoleHandler());
this.registerHandler(new MakerKitBillingCycleHandler());
this.registerHandler(new MakerKitOrganizationOwnerHandler());
this.registerHandler(new MakerKitInvitationStatusHandler());
// Enhanced generic handlers
this.registerHandler(new GenericCheckConstraintHandler());
this.registerHandler(new GenericForeignKeyHandler());
this.registerHandler(new GenericUniqueConstraintHandler());
this.registerHandler(new GenericNotNullHandler());
logger_1.Logger.debug(`Initialized ${this.handlers.size} constraint handlers`);
}
/**
* Register a custom constraint handler
*/
static registerHandler(handler) {
this.handlers.set(handler.id, handler);
logger_1.Logger.debug(`Registered constraint handler: ${handler.id}`);
}
/**
* Get handler for a specific constraint
*/
static getHandler(constraint, constraintType) {
// Try to find specific handler first
for (const handler of this.handlers.values()) {
if (handler.type === constraintType && handler.canHandle(constraint, null)) {
return handler;
}
}
// Fall back to generic handler
const genericHandlerKey = `generic_${constraintType}`;
return this.handlers.get(genericHandlerKey) || null;
}
/**
* Handle constraint for given data
*/
static handleConstraint(constraint, constraintType, data) {
const handler = this.getHandler(constraint, constraintType);
if (!handler) {
return {
success: false,
originalData: data,
modifiedData: data,
appliedFixes: [],
warnings: [`No handler found for ${constraintType} constraint`],
errors: [],
bypassRequired: true
};
}
try {
return handler.handle(constraint, data);
}
catch (error) {
logger_1.Logger.error(`Constraint handler ${handler.id} failed:`, error);
return {
success: false,
originalData: data,
modifiedData: data,
appliedFixes: [],
warnings: [],
errors: [`Handler error: ${error.message}`],
bypassRequired: true
};
}
}
/**
* Get all registered handlers
*/
static getAllHandlers() {
return Array.from(this.handlers.values());
}
/**
* Clear all handlers
*/
static clearHandlers() {
this.handlers.clear();
}
}
exports.ConstraintHandlers = ConstraintHandlers;
ConstraintHandlers.handlers = new Map();
/**
* MakerKit Team Account Slug Handler
* Handles slug generation and validation for team accounts
*/
class MakerKitTeamAccountSlugHandler {
constructor() {
this.id = 'makerkit_team_account_slug';
this.type = 'check';
this.priority = 95;
this.description = 'Handles MakerKit team account slug generation and validation';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('accounts_slug') ||
constraint.constraintName?.toLowerCase().includes('team_slug') ||
(constraint.checkClause?.toLowerCase().includes('slug') &&
constraint.checkClause?.toLowerCase().includes('is_personal_account')));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
// If is_personal_account is false and no slug provided, generate one
if (data.is_personal_account === false && !data.slug) {
const generatedSlug = this.generateUniqueSlug(data);
result.modifiedData.slug = generatedSlug;
result.appliedFixes.push({
type: 'set_field',
field: 'slug',
oldValue: null,
newValue: generatedSlug,
reason: 'Generated unique slug for team account',
confidence: 0.9
});
}
// Validate existing slug format for team accounts
if (data.is_personal_account === false && data.slug) {
const validatedSlug = this.validateAndFixSlug(data.slug);
if (validatedSlug !== data.slug) {
result.modifiedData.slug = validatedSlug;
result.appliedFixes.push({
type: 'transform_value',
field: 'slug',
oldValue: data.slug,
newValue: validatedSlug,
reason: 'Normalized slug format for team account',
confidence: 0.95
});
}
}
return result;
}
generateUniqueSlug(data) {
const baseName = data.name || data.organization_name || data.account_name || 'team';
let slug = this.normalizeSlug(baseName);
// Add timestamp to ensure uniqueness in testing scenarios
const timestamp = Date.now().toString().slice(-6);
slug = `${slug}-${timestamp}`;
return slug;
}
validateAndFixSlug(slug) {
return this.normalizeSlug(slug);
}
normalizeSlug(input) {
return input
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Replace multiple hyphens with single
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
.substring(0, 50); // Limit length
}
}
/**
* MakerKit Account Type Handler
* Handles account type validation and business logic constraints
*/
class MakerKitAccountTypeHandler {
constructor() {
this.id = 'makerkit_account_type';
this.type = 'check';
this.priority = 90;
this.description = 'Handles MakerKit account type constraints';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('account_type') ||
constraint.checkClause?.toLowerCase().includes('account_type') ||
constraint.checkClause?.toLowerCase().includes('is_personal_account'));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
const validAccountTypes = ['personal', 'team', 'organization', 'enterprise'];
// Handle is_personal_account boolean field
if (data.hasOwnProperty('is_personal_account')) {
if (data.is_personal_account === true) {
if (data.account_type && data.account_type !== 'personal') {
result.modifiedData.account_type = 'personal';
result.appliedFixes.push({
type: 'set_field',
field: 'account_type',
oldValue: data.account_type,
newValue: 'personal',
reason: 'Account type must be personal when is_personal_account is true',
confidence: 0.95
});
}
}
else if (data.is_personal_account === false) {
if (!data.account_type || data.account_type === 'personal') {
result.modifiedData.account_type = 'team';
result.appliedFixes.push({
type: 'set_field',
field: 'account_type',
oldValue: data.account_type,
newValue: 'team',
reason: 'Default to team account type when is_personal_account is false',
confidence: 0.85
});
}
}
}
// Validate account_type field
if (data.account_type && !validAccountTypes.includes(data.account_type)) {
result.modifiedData.account_type = 'personal';
result.appliedFixes.push({
type: 'set_field',
field: 'account_type',
oldValue: data.account_type,
newValue: 'personal',
reason: 'Invalid account type, defaulting to personal',
confidence: 0.8
});
}
return result;
}
}
/**
* MakerKit User Role Handler
* Handles user role validation and hierarchy constraints
*/
class MakerKitUserRoleHandler {
constructor() {
this.id = 'makerkit_user_role';
this.type = 'check';
this.priority = 85;
this.description = 'Handles MakerKit user role constraints';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('user_role') ||
constraint.constraintName?.toLowerCase().includes('member_role') ||
constraint.checkClause?.toLowerCase().includes('role'));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
const validRoles = ['owner', 'admin', 'member', 'viewer', 'billing', 'support'];
const roleHierarchy = {
owner: 5,
admin: 4,
billing: 3,
support: 2,
member: 1,
viewer: 0
};
// Validate role field
if (data.role && !validRoles.includes(data.role)) {
result.modifiedData.role = 'member';
result.appliedFixes.push({
type: 'set_field',
field: 'role',
oldValue: data.role,
newValue: 'member',
reason: 'Invalid role, defaulting to member',
confidence: 0.8
});
}
// Set default role if missing
if (!data.role) {
const defaultRole = data.is_owner ? 'owner' : 'member';
result.modifiedData.role = defaultRole;
result.appliedFixes.push({
type: 'set_field',
field: 'role',
oldValue: undefined,
newValue: defaultRole,
reason: `Set default role based on ${data.is_owner ? 'owner status' : 'member status'}`,
confidence: 0.9
});
}
// Validate role hierarchy constraints
if (data.role === 'owner' && data.is_owner !== true) {
result.modifiedData.is_owner = true;
result.appliedFixes.push({
type: 'set_field',
field: 'is_owner',
oldValue: data.is_owner,
newValue: true,
reason: 'Owner role requires is_owner flag to be true',
confidence: 0.95
});
}
return result;
}
}
/**
* MakerKit Billing Cycle Handler
* Handles billing cycle and subscription constraints
*/
class MakerKitBillingCycleHandler {
constructor() {
this.id = 'makerkit_billing_cycle';
this.type = 'check';
this.priority = 80;
this.description = 'Handles MakerKit billing cycle constraints';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('billing_cycle') ||
constraint.constraintName?.toLowerCase().includes('interval') ||
constraint.checkClause?.toLowerCase().includes('billing') ||
constraint.checkClause?.toLowerCase().includes('interval'));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
const validCycles = ['monthly', 'yearly', 'weekly', 'daily'];
const validIntervals = ['month', 'year', 'week', 'day'];
// Handle billing_cycle field
if (data.billing_cycle && !validCycles.includes(data.billing_cycle)) {
result.modifiedData.billing_cycle = 'monthly';
result.appliedFixes.push({
type: 'set_field',
field: 'billing_cycle',
oldValue: data.billing_cycle,
newValue: 'monthly',
reason: 'Invalid billing cycle, defaulting to monthly',
confidence: 0.85
});
}
// Handle interval field
if (data.interval && !validIntervals.includes(data.interval)) {
result.modifiedData.interval = 'month';
result.appliedFixes.push({
type: 'set_field',
field: 'interval',
oldValue: data.interval,
newValue: 'month',
reason: 'Invalid interval, defaulting to month',
confidence: 0.85
});
}
// Set default values if missing
if (!data.billing_cycle && !data.interval) {
result.modifiedData.billing_cycle = 'monthly';
result.appliedFixes.push({
type: 'set_field',
field: 'billing_cycle',
oldValue: undefined,
newValue: 'monthly',
reason: 'Set default billing cycle',
confidence: 0.9
});
}
return result;
}
}
/**
* MakerKit Organization Owner Handler
* Handles organization ownership constraints
*/
class MakerKitOrganizationOwnerHandler {
constructor() {
this.id = 'makerkit_organization_owner';
this.type = 'unique';
this.priority = 85;
this.description = 'Handles MakerKit organization owner constraints';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('organization_owner') ||
constraint.constraintName?.toLowerCase().includes('account_owner') ||
(constraint.columns?.includes('organization_id') &&
(constraint.columns?.includes('is_owner') || constraint.columns?.includes('owner_id'))));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
// Validate organization ownership logic
if (data.is_owner === true && !data.organization_id) {
result.warnings.push('Owner flag set but no organization_id provided');
}
// Handle role consistency with ownership
if (data.is_owner === true && data.role && data.role !== 'owner') {
result.modifiedData.role = 'owner';
result.appliedFixes.push({
type: 'set_field',
field: 'role',
oldValue: data.role,
newValue: 'owner',
reason: 'Role must be owner when is_owner is true',
confidence: 0.95
});
}
return result;
}
}
/**
* MakerKit Invitation Status Handler
* Handles invitation status constraints and workflows
*/
class MakerKitInvitationStatusHandler {
constructor() {
this.id = 'makerkit_invitation_status';
this.type = 'check';
this.priority = 75;
this.description = 'Handles MakerKit invitation status constraints';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('invitation_status') ||
constraint.constraintName?.toLowerCase().includes('invite_status') ||
constraint.checkClause?.toLowerCase().includes('status') &&
(constraint.checkClause?.toLowerCase().includes('pending') ||
constraint.checkClause?.toLowerCase().includes('accepted') ||
constraint.checkClause?.toLowerCase().includes('declined')));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
const validStatuses = ['pending', 'accepted', 'declined', 'expired'];
// Validate invitation status
if (data.status && !validStatuses.includes(data.status)) {
result.modifiedData.status = 'pending';
result.appliedFixes.push({
type: 'set_field',
field: 'status',
oldValue: data.status,
newValue: 'pending',
reason: 'Invalid invitation status, defaulting to pending',
confidence: 0.85
});
}
// Set default status if missing
if (!data.status) {
result.modifiedData.status = 'pending';
result.appliedFixes.push({
type: 'set_field',
field: 'status',
oldValue: undefined,
newValue: 'pending',
reason: 'Set default invitation status',
confidence: 0.9
});
}
// Handle invitation expiry logic
if (data.status === 'pending' && data.expires_at) {
const now = new Date();
const expiryDate = new Date(data.expires_at);
if (expiryDate < now) {
result.modifiedData.status = 'expired';
result.appliedFixes.push({
type: 'set_field',
field: 'status',
oldValue: 'pending',
newValue: 'expired',
reason: 'Invitation has expired based on expires_at date',
confidence: 0.95
});
}
}
return result;
}
}
/**
* MakerKit Personal Account Slug Handler
* Handles the accounts_slug_null_if_personal_account_true constraint
*/
class MakerKitPersonalAccountSlugHandler {
constructor() {
this.id = 'makerkit_personal_account_slug';
this.type = 'check';
this.priority = 100;
this.description = 'Handles MakerKit personal account slug constraint';
}
canHandle(constraint, data) {
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
});
}
// If is_personal_account is not set, default to true for profile creation
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.85
});
result.appliedFixes.push({
type: 'set_field',
field: 'slug',
oldValue: data.slug,
newValue: null,
reason: 'Set slug to null for personal account',
confidence: 0.95
});
}
return result;
}
generateFix(constraint, data) {
return {
type: 'set_field',
field: 'slug',
oldValue: data.slug,
newValue: null,
reason: 'MakerKit personal accounts require null slug',
confidence: 0.95
};
}
}
/**
* MakerKit Organization Member Handler
*/
class MakerKitOrganizationMemberHandler {
constructor() {
this.id = 'makerkit_organization_member';
this.type = 'unique';
this.priority = 90;
this.description = 'Handles MakerKit organization member uniqueness';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('organization_member') ||
(constraint.columns?.includes('organization_id') && constraint.columns?.includes('user_id')));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
// Check if both organization_id and user_id are present
if (!data.organization_id || !data.user_id) {
result.warnings.push('Organization member requires both organization_id and user_id');
}
// Could add logic to check for existing memberships and suggest alternatives
return result;
}
}
/**
* MakerKit Subscription Status Handler
*/
class MakerKitSubscriptionStatusHandler {
constructor() {
this.id = 'makerkit_subscription_status';
this.type = 'check';
this.priority = 85;
this.description = 'Handles MakerKit subscription status validation';
}
canHandle(constraint, data) {
return (constraint.constraintName?.toLowerCase().includes('subscription_status') ||
constraint.checkClause?.toLowerCase().includes('status') &&
constraint.checkClause?.toLowerCase().includes('active'));
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
const validStatuses = ['active', 'canceled', 'past_due', 'trialing', 'incomplete'];
if (data.status && !validStatuses.includes(data.status)) {
result.modifiedData.status = 'active'; // Default to active
result.appliedFixes.push({
type: 'set_field',
field: 'status',
oldValue: data.status,
newValue: 'active',
reason: 'Invalid subscription status, defaulting to active',
confidence: 0.8
});
}
if (!data.status) {
result.modifiedData.status = 'active';
result.appliedFixes.push({
type: 'set_field',
field: 'status',
oldValue: undefined,
newValue: 'active',
reason: 'Set default subscription status',
confidence: 0.9
});
}
return result;
}
}
/**
* Generic Check Constraint Handler
*/
class GenericCheckConstraintHandler {
constructor() {
this.id = 'generic_check';
this.type = 'check';
this.priority = 10;
this.description = 'Generic handler for check constraints';
}
canHandle(constraint, data) {
return true; // Generic handler can handle any check constraint
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [`Generic handling for check constraint: ${constraint.constraintName}`],
errors: [],
bypassRequired: false
};
// Basic analysis of check clause
const checkClause = constraint.checkClause?.toLowerCase() || '';
// Handle common patterns
if (checkClause.includes('not null') || checkClause.includes('is not null')) {
this.handleNotNullCheck(constraint, data, result);
}
else if (checkClause.includes('length(') || checkClause.includes('char_length(')) {
this.handleLengthCheck(constraint, data, result);
}
else if (checkClause.includes(' in (')) {
this.handleEnumCheck(constraint, data, result);
}
else {
result.warnings.push(`Complex check constraint may require manual review: ${constraint.constraintName}`);
}
return result;
}
handleNotNullCheck(constraint, data, result) {
// Extract field name from constraint (basic pattern matching)
const checkClause = constraint.checkClause;
const fieldMatch = checkClause.match(/(\w+)\s+(IS\s+)?NOT\s+NULL/i);
if (fieldMatch) {
const fieldName = fieldMatch[1];
if (data[fieldName] === null || data[fieldName] === undefined) {
result.warnings.push(`Field ${fieldName} should not be null according to constraint ${constraint.constraintName}`);
}
}
}
handleLengthCheck(constraint, data, result) {
result.warnings.push(`Length constraint detected: ${constraint.constraintName} - manual validation recommended`);
}
handleEnumCheck(constraint, data, result) {
result.warnings.push(`Enum constraint detected: ${constraint.constraintName} - validate against allowed values`);
}
}
/**
* Generic Foreign Key Handler
*/
class GenericForeignKeyHandler {
constructor() {
this.id = 'generic_foreign_key';
this.type = 'foreign_key';
this.priority = 10;
this.description = 'Generic handler for foreign key constraints';
}
canHandle(constraint, data) {
return true;
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
const fieldValue = data[constraint.columnName];
if (fieldValue === null || fieldValue === undefined) {
if (constraint.onDelete === 'SET NULL') {
// This is acceptable
result.warnings.push(`Foreign key ${constraint.columnName} is null, which is allowed by constraint`);
}
else {
result.warnings.push(`Foreign key ${constraint.columnName} is null - ensure referenced record exists`);
}
}
else {
result.warnings.push(`Foreign key ${constraint.columnName} references ${constraint.referencedTable}.${constraint.referencedColumn} - ensure target exists`);
}
return result;
}
}
/**
* Generic Unique Constraint Handler
*/
class GenericUniqueConstraintHandler {
constructor() {
this.id = 'generic_unique';
this.type = 'unique';
this.priority = 10;
this.description = 'Generic handler for unique constraints';
}
canHandle(constraint, data) {
return true;
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
// Check if all columns in unique constraint have values
const missingColumns = constraint.columns.filter(col => data[col] === null || data[col] === undefined);
if (missingColumns.length > 0) {
result.warnings.push(`Unique constraint ${constraint.constraintName} requires values for: ${missingColumns.join(', ')}`);
}
else {
result.warnings.push(`Unique constraint ${constraint.constraintName} - ensure values are unique across: ${constraint.columns.join(', ')}`);
}
return result;
}
}
/**
* Generic Not Null Handler
*/
class GenericNotNullHandler {
constructor() {
this.id = 'generic_not_null';
this.type = 'not_null';
this.priority = 10;
this.description = 'Generic handler for not null constraints';
}
canHandle(constraint, data) {
return true;
}
handle(constraint, data) {
const result = {
success: true,
originalData: { ...data },
modifiedData: { ...data },
appliedFixes: [],
warnings: [],
errors: [],
bypassRequired: false
};
const fieldName = constraint.columnName;
if (data[fieldName] === null || data[fieldName] === undefined) {
result.errors.push(`Field ${fieldName} cannot be null`);
result.success = false;
}
return result;
}
}
// Initialize default handlers when module is loaded
ConstraintHandlers.initializeDefaultHandlers();
//# sourceMappingURL=constraint-handlers.js.map