supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
301 lines • 13.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseDataSeeder = void 0;
const base_seeder_1 = require("./base-seeder");
const logger_1 = require("../../../core/utils/logger");
const domains_1 = require("../../../domains");
const table_mapping_resolver_1 = require("../../../schema/table-mapping-resolver");
class BaseDataSeeder extends base_seeder_1.BaseSeeder {
/**
* Get the correct table name for base templates using dynamic mapping
*/
async getBaseTemplateTableName() {
try {
const framework = this.context.config.schema?.framework || 'makerkit';
const resolver = (0, table_mapping_resolver_1.createTableMappingResolver)(this.context.client, {
framework,
validateWithDatabase: true
});
const result = await resolver.resolveTableName('setup_types', 'baseTemplateTable');
if (result.warnings.length > 0) {
logger_1.Logger.warn('⚠️ Base template table mapping warnings:', result.warnings);
}
logger_1.Logger.debug(`🗺️ Base template table resolved: setup_types -> ${result.actualTableName}`, {
framework,
exists: result.exists,
source: result.source
});
return result.actualTableName;
}
catch (error) {
logger_1.Logger.warn(`⚠️ Failed to resolve base template table name, using fallback 'base_templates':`, error);
return 'base_templates';
}
}
/**
* Check if a column exists in a table (helper method)
*/
async columnExists(tableName, columnName) {
try {
const { error } = await this.context.client
.from(tableName)
.select(columnName)
.limit(1);
return !error;
}
catch {
return false;
}
}
/**
* Get the correct column name for a field, checking config and existence
*/
async getColumnMapping(tableName, expectedField, fallbacks = []) {
// First check if config override specifies a mapping
const configMapping = this.getConfigColumnMapping(tableName, expectedField);
if (configMapping && await this.columnExists(tableName, configMapping)) {
return configMapping;
}
// Fall back to checking all options
const allOptions = [expectedField, ...fallbacks];
for (const option of allOptions) {
if (await this.columnExists(tableName, option)) {
return option;
}
}
return null;
}
/**
* Get column mapping from configuration with enhanced support
*/
getConfigColumnMapping(tableName, expectedField) {
if (!this.context.config?.schema)
return null;
// Handle base_templates table mappings
if (tableName === 'base_templates' && this.context.config.schema?.baseTemplateTable) {
const table = this.context.config.schema.baseTemplateTable;
switch (expectedField) {
case 'description': return table.descriptionField || null;
case 'type': return table.typeField || null;
case 'make': return table.makeField || null;
case 'model': return table.modelField || null;
case 'year': return table.yearField || null;
}
}
// Handle profiles table mappings
if (tableName === 'profiles' && this.context.config.schema?.userTable) {
const table = this.context.config.schema.userTable;
switch (expectedField) {
case 'picture_url':
case 'avatar_url': return table.pictureField || null;
case 'bio': return table.bioField || null;
case 'name':
case 'display_name': return table.nameField || null;
}
}
return null;
}
async seed() {
console.log('🗂️ Seeding base data...');
await this.seedCategories();
await this.seedBaseTemplates();
console.log('✅ Base data seeding complete');
}
async seedCategories() {
const domainConfig = (0, domains_1.getDomainConfig)(this.context.config.domain);
const categories = domainConfig.categories;
await this.seedWithFallback(async () => {
const { client } = this.context;
const { data: existingCategories, error: selectError } = await client
.from('categories')
.select('name');
if (selectError) {
throw new Error(`Failed to check existing categories: ${selectError.message}`);
}
const existingNames = new Set(existingCategories?.map(c => c.name) || []);
const newCategories = categories.filter(cat => !existingNames.has(cat.name));
if (newCategories.length > 0) {
const { error: insertError } = await client
.from('categories')
.insert(newCategories);
if (insertError) {
throw insertError;
}
logger_1.Logger.complete(`Created ${newCategories.length} categories`);
}
else {
logger_1.Logger.info('Categories already exist, skipping');
}
}, 'categories', 'Categories are optional for basic seeding');
}
async seedBaseTemplates() {
// Get schema adapter to check column existence
const schemaAdapter = this.context.cache.get('schemaAdapter');
// Get the correct table name for base templates
const baseTemplateTableName = await this.getBaseTemplateTableName();
// Check what description column to use based on config and existence
const descriptionColumn = await this.getColumnMapping(baseTemplateTableName, 'description', ['info', 'details', 'notes']);
const hasDescriptionColumn = descriptionColumn !== null;
const baseTemplateData = [
// Vehicle Templates
{
type: 'Vehicle',
make: 'Toyota',
model: 'Tacoma',
year: 2023,
description: 'Mid-size pickup truck popular for overlanding'
},
{
type: 'Vehicle',
make: 'Toyota',
model: '4Runner',
year: 2023,
description: 'Full-size SUV with excellent off-road capability'
},
{
type: 'Vehicle',
make: 'Jeep',
model: 'Wrangler',
year: 2023,
description: 'Iconic 4x4 vehicle for trail adventures'
},
{
type: 'Vehicle',
make: 'Ford',
model: 'Bronco',
year: 2023,
description: 'Modern off-road SUV with classic heritage'
},
{
type: 'Vehicle',
make: 'Subaru',
model: 'Outback',
year: 2023,
description: 'All-wheel drive wagon for car camping'
},
// Backpack Templates
{
type: 'Backpack',
make: 'Osprey',
model: 'Atmos AG 65',
description: 'Lightweight backpacking pack with anti-gravity suspension'
},
{
type: 'Backpack',
make: 'Gregory',
model: 'Baltoro 65',
description: 'Traditional backpacking pack with excellent load support'
},
{
type: 'Backpack',
make: 'Hyperlite Mountain Gear',
model: 'Southwest 55',
description: 'Ultralight backpacking pack for minimalist hiking'
},
{
type: 'Backpack',
make: 'Kelty',
model: 'Coyote 65',
description: 'Affordable, durable pack for weekend adventures'
},
{
type: 'Backpack',
make: 'Deuter',
model: 'Aircontact Lite 65+10',
description: 'European-style pack with excellent ventilation'
},
];
// Use correct description column or remove if none exists
const templates = hasDescriptionColumn ?
baseTemplateData.map(template => {
if (descriptionColumn !== 'description') {
// Rename description field to match actual column
const { description, ...rest } = template;
return { ...rest, [descriptionColumn]: description };
}
return template;
}) :
baseTemplateData.map(({ description, ...rest }) => rest);
await this.seedWithFallback(async () => {
const { client } = this.context;
const baseTemplateTableName = await this.getBaseTemplateTableName();
const { data: existingTemplates, error: selectError } = await client
.from(baseTemplateTableName)
.select('make, model, type');
if (selectError) {
throw new Error(`Failed to check existing templates: ${selectError.message}`);
}
const existingKeys = new Set(existingTemplates?.map(t => `${t.type}-${t.make}-${t.model}`) || []);
const newTemplates = templates.filter(template => !existingKeys.has(`${template.type}-${template.make}-${template.model}`));
if (newTemplates.length > 0) {
const { error: insertError } = await client
.from(baseTemplateTableName)
.insert(newTemplates);
if (insertError) {
// Provide better error message for column issues
const errorMsg = insertError.message.includes('column') && !hasDescriptionColumn ?
`Base template creation failed: description column not found. ${insertError.message}` :
insertError.message;
throw new Error(errorMsg);
}
const msg = hasDescriptionColumn ?
`Created ${newTemplates.length} base templates` :
`Created ${newTemplates.length} base templates (without descriptions - column not found)`;
logger_1.Logger.complete(msg);
}
else {
logger_1.Logger.info('Base templates already exist, skipping');
}
// Cache templates for other seeders
const { data: allTemplates } = await client
.from(baseTemplateTableName)
.select('*');
this.context.cache.set('baseTemplates', allTemplates || []);
}, await this.getBaseTemplateTableName(), 'Base templates are optional for basic seeding');
// Ensure cache has at least empty array if table doesn't exist
if (!this.context.cache.has('baseTemplates')) {
this.context.cache.set('baseTemplates', []);
}
}
/**
* Legacy function for backward compatibility
* @deprecated Use seedCategories instead
*/
async seedGearCategories() {
const categories = [
{ name: 'Shelter', description: 'Tents, tarps, and protective gear' },
{ name: 'Sleep System', description: 'Sleeping bags, pads, and comfort items' },
{ name: 'Cooking', description: 'Stoves, cookware, and food preparation' },
{ name: 'Navigation', description: 'Maps, compass, GPS, and wayfinding tools' },
{ name: 'Safety', description: 'First aid, emergency, and safety equipment' },
{ name: 'Clothing', description: 'Base layers, shells, and outdoor apparel' },
{ name: 'Electronics', description: 'Lights, batteries, communication devices' },
{ name: 'Tools', description: 'Knives, multi-tools, and utility items' },
{ name: 'Hydration', description: 'Water bottles, filters, and treatment' },
{ name: 'Vehicle', description: 'Overland and camping vehicle modifications' },
];
// Use the main seedCategories logic but with hardcoded categories
await this.seedWithFallback(async () => {
const { client } = this.context;
const { data: existingCategories, error: selectError } = await client
.from('categories')
.select('name');
if (selectError) {
throw new Error(`Failed to check existing categories: ${selectError.message}`);
}
const existingNames = new Set(existingCategories?.map(c => c.name) || []);
const newCategories = categories.filter(cat => !existingNames.has(cat.name));
if (newCategories.length > 0) {
const { error: insertError } = await client.from('categories').insert(newCategories);
if (insertError)
throw insertError;
logger_1.Logger.complete(`Created ${newCategories.length} categories`);
}
else {
logger_1.Logger.info('Categories already exist, skipping');
}
}, 'categories', 'Categories are optional for basic seeding');
}
}
exports.BaseDataSeeder = BaseDataSeeder;
//# sourceMappingURL=base-data-seeder.js.map