supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
442 lines • 16.3 kB
JavaScript
;
/**
* Asset Selection Strategies
* Phase 2, Checkpoint B2 - Intelligent asset selection algorithms
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AssetSelectionEngine = void 0;
const logger_1 = require("../../../core/utils/logger");
class AssetSelectionEngine {
/**
* Select all assets from the pool (no filtering)
*/
static selectAll(assets, options = {}) {
const startTime = Date.now();
let selected = [...assets];
// Apply ordering if requested
if (!options.preserveOrder) {
// Default ordering: by last modified date (newest first)
selected.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
}
// Apply count limit if specified
if (options.count && options.count < selected.length) {
selected = selected.slice(0, options.count);
}
const executionTime = Date.now() - startTime;
return {
selected,
total: assets.length,
strategy: 'all',
metadata: this.calculateSelectionMetadata(selected, assets.length, executionTime)
};
}
/**
* Select random assets with optional seed for deterministic results
*/
static selectRandom(assets, count, options = {}) {
const startTime = Date.now();
if (count >= assets.length) {
// If requesting more than available, return all
return this.selectAll(assets, options);
}
const seed = options.seed || this.DEFAULT_SEED;
const rng = this.createSeededRandom(seed);
// Create shuffled copy for selection
const shuffled = [...assets];
// Fisher-Yates shuffle with seeded random
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
const selected = shuffled.slice(0, count);
// Restore original order if requested
if (options.preserveOrder) {
selected.sort((a, b) => {
const aIndex = assets.indexOf(a);
const bIndex = assets.indexOf(b);
return aIndex - bIndex;
});
}
const executionTime = Date.now() - startTime;
return {
selected,
total: assets.length,
strategy: 'random',
seed,
metadata: this.calculateSelectionMetadata(selected, assets.length, executionTime)
};
}
/**
* Select assets using custom filter functions
*/
static selectFiltered(assets, filters, options = {}) {
const startTime = Date.now();
let filtered = assets;
// Apply each filter sequentially
for (const filter of filters) {
filtered = filtered.filter((asset, index, array) => {
try {
return filter(asset, index, array);
}
catch (error) {
logger_1.Logger.warn(`Filter function error for asset ${asset.id}: ${error.message}`);
return false; // Exclude asset if filter fails
}
});
}
// Apply count limit if specified
if (options.count && options.count < filtered.length) {
// If deterministic is requested, use seeded selection
if (options.ensureDeterministic) {
const randomResult = this.selectRandom(filtered, options.count, options);
randomResult.strategy = 'filtered+random';
return randomResult;
}
else {
filtered = filtered.slice(0, options.count);
}
}
const executionTime = Date.now() - startTime;
return {
selected: filtered,
total: assets.length,
strategy: 'filtered',
metadata: this.calculateSelectionMetadata(filtered, assets.length, executionTime)
};
}
/**
* Select specific assets by file names or IDs
*/
static selectManual(assets, identifiers, options = {}) {
const startTime = Date.now();
const selected = [];
const notFound = [];
for (const identifier of identifiers) {
// Try to find by ID first, then by filename, then by file path
const asset = assets.find(a => a.id === identifier ||
a.metadata.filename === identifier ||
a.filePath.includes(identifier) ||
a.filePath === identifier);
if (asset) {
selected.push(asset);
}
else {
notFound.push(identifier);
}
}
if (notFound.length > 0) {
logger_1.Logger.warn(`Manual selection: ${notFound.length} assets not found: ${notFound.join(', ')}`);
}
// Preserve order of identifiers unless specified otherwise
if (!options.preserveOrder) {
// Sort by original asset order
selected.sort((a, b) => {
const aIndex = assets.indexOf(a);
const bIndex = assets.indexOf(b);
return aIndex - bIndex;
});
}
const executionTime = Date.now() - startTime;
return {
selected,
total: assets.length,
strategy: 'manual',
metadata: this.calculateSelectionMetadata(selected, assets.length, executionTime)
};
}
/**
* Select assets using weighted probability distribution
*/
static selectWeighted(assets, count, config, options = {}) {
const startTime = Date.now();
if (count >= assets.length) {
return this.selectAll(assets, options);
}
// Calculate weights for each asset
const weights = assets.map((asset, index) => {
let weight = 1; // Base weight
// Apply custom weight function if provided
if (config.weightFunction) {
try {
weight = config.weightFunction(asset, index, assets);
}
catch (error) {
logger_1.Logger.warn(`Weight function error for asset ${asset.id}: ${error.message}`);
weight = 1; // Fallback to base weight
}
}
// Apply static weights
if (config.weights) {
const staticWeight = config.weights[asset.id] || config.weights[asset.type];
if (staticWeight !== undefined) {
weight *= staticWeight;
}
}
// Apply biases
if (config.biases) {
weight *= this.calculateBiasWeight(asset, config.biases, assets);
}
return Math.max(0, weight); // Ensure non-negative
});
// Perform weighted selection
const selected = this.performWeightedSelection(assets, weights, count, options);
const executionTime = Date.now() - startTime;
return {
selected,
total: assets.length,
strategy: 'weighted',
seed: options.seed,
metadata: this.calculateSelectionMetadata(selected, assets.length, executionTime)
};
}
/**
* Create a seeded random number generator
*/
static createSeededRandom(seed) {
let hash = 0;
for (let i = 0; i < seed.length; i++) {
const char = seed.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
// Use the hash as seed for a simple PRNG (Mulberry32)
let state = Math.abs(hash);
return function () {
state |= 0;
state = state + 0x6D2B79F5 | 0;
let t = Math.imul(state ^ state >>> 15, state | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
/**
* Calculate bias weight based on asset characteristics
*/
static calculateBiasWeight(asset, biases, allAssets) {
let biasMultiplier = 1;
// Type bias
if (biases.byType && biases.byType[asset.type]) {
biasMultiplier *= biases.byType[asset.type];
}
// Size bias
if (biases.bySize && biases.bySize !== 'neutral') {
const avgSize = allAssets.reduce((sum, a) => sum + a.fileSize, 0) / allAssets.length;
if (biases.bySize === 'prefer_small' && asset.fileSize < avgSize) {
biasMultiplier *= 1.5;
}
else if (biases.bySize === 'prefer_large' && asset.fileSize > avgSize) {
biasMultiplier *= 1.5;
}
}
// Age bias
if (biases.byAge && biases.byAge !== 'neutral') {
const now = Date.now();
const avgAge = allAssets.reduce((sum, a) => sum + (now - a.lastModified.getTime()), 0) / allAssets.length;
const assetAge = now - asset.lastModified.getTime();
if (biases.byAge === 'prefer_new' && assetAge < avgAge) {
biasMultiplier *= 1.5;
}
else if (biases.byAge === 'prefer_old' && assetAge > avgAge) {
biasMultiplier *= 1.5;
}
}
// Tag bias
if (biases.byTags && asset.metadata.tags) {
const tags = Array.isArray(asset.metadata.tags) ? asset.metadata.tags : [];
for (const tag of tags) {
if (biases.byTags[tag]) {
biasMultiplier *= biases.byTags[tag];
}
}
}
return biasMultiplier;
}
/**
* Perform weighted selection using cumulative probability
*/
static performWeightedSelection(assets, weights, count, options) {
const seed = options.seed || this.DEFAULT_SEED;
const rng = this.createSeededRandom(seed);
// Calculate cumulative weights
const totalWeight = weights.reduce((sum, w) => sum + w, 0);
if (totalWeight === 0) {
logger_1.Logger.warn('All assets have zero weight, falling back to random selection');
return this.selectRandom(assets, count, options).selected;
}
const cumulativeWeights = weights.reduce((acc, weight, index) => {
acc.push((acc[index - 1] || 0) + weight);
return acc;
}, []);
const selected = [];
const selectedIndices = new Set();
// Perform selection
for (let i = 0; i < count && selectedIndices.size < assets.length; i++) {
let attempts = 0;
let selectedIndex = -1;
// Try to find a unique asset (with reasonable attempt limit)
while (attempts < 100 && (selectedIndex === -1 || selectedIndices.has(selectedIndex))) {
const random = rng() * totalWeight;
selectedIndex = cumulativeWeights.findIndex(cw => cw >= random);
attempts++;
}
if (selectedIndex !== -1 && !selectedIndices.has(selectedIndex)) {
selected.push(assets[selectedIndex]);
selectedIndices.add(selectedIndex);
}
}
// If we couldn't get enough unique assets, fill with remaining assets
if (selected.length < count) {
const remaining = assets.filter((_, index) => !selectedIndices.has(index));
selected.push(...remaining.slice(0, count - selected.length));
}
return selected;
}
/**
* Calculate metadata for selection results
*/
static calculateSelectionMetadata(selected, totalAssets, executionTime) {
const typeDistribution = {};
let totalSize = 0;
let validAssets = 0;
for (const asset of selected) {
typeDistribution[asset.type] = (typeDistribution[asset.type] || 0) + 1;
totalSize += asset.fileSize;
if (asset.isValid)
validAssets++;
}
return {
selectionRate: totalAssets > 0 ? (selected.length / totalAssets) * 100 : 0,
avgFileSize: selected.length > 0 ? totalSize / selected.length : 0,
typeDistribution,
validAssets,
executionTime
};
}
}
exports.AssetSelectionEngine = AssetSelectionEngine;
AssetSelectionEngine.DEFAULT_SEED = 'supa-seed-default';
/**
* Create common filter functions for convenience
*/
AssetSelectionEngine.createFilters = {
/**
* Filter by asset type
*/
byType: (types) => {
return (asset) => types.includes(asset.type);
},
/**
* Filter by tags (asset must have at least one of the specified tags)
*/
byTags: (tags) => {
return (asset) => {
const assetTags = asset.metadata.tags || [];
return tags.some(tag => assetTags.includes(tag));
};
},
/**
* Filter by required tags (asset must have all specified tags)
*/
byRequiredTags: (tags) => {
return (asset) => {
const assetTags = asset.metadata.tags || [];
return tags.every(tag => assetTags.includes(tag));
};
},
/**
* Filter by category
*/
byCategory: (categories) => {
return (asset) => categories.includes(asset.metadata.category || '');
},
/**
* Filter by file size range
*/
bySizeRange: (minSize, maxSize) => {
return (asset) => asset.fileSize >= minSize && asset.fileSize <= maxSize;
},
/**
* Filter by date range
*/
byDateRange: (startDate, endDate) => {
return (asset) => {
const assetDate = asset.lastModified;
return assetDate >= startDate && assetDate <= endDate;
};
},
/**
* Filter by content length
*/
byContentLength: (minLength, maxLength) => {
return (asset) => {
if (!asset.content)
return false;
const length = asset.content.length;
return length >= minLength && (maxLength === undefined || length <= maxLength);
};
},
/**
* Filter valid assets only
*/
validOnly: () => {
return (asset) => asset.isValid;
},
/**
* Filter by metadata field existence
*/
hasField: (fieldName) => {
return (asset) => asset.metadata[fieldName] !== undefined;
},
/**
* Filter by metadata field value
*/
fieldEquals: (fieldName, value) => {
return (asset) => asset.metadata[fieldName] === value;
}
};
/**
* Create common weight functions for convenience
*/
AssetSelectionEngine.createWeightFunctions = {
/**
* Weight by file size (larger files get higher weight)
*/
byFileSize: () => {
return (asset, index, assets) => {
const maxSize = Math.max(...assets.map(a => a.fileSize));
return maxSize > 0 ? asset.fileSize / maxSize : 1;
};
},
/**
* Weight by recency (newer files get higher weight)
*/
byRecency: () => {
return (asset, index, assets) => {
const now = Date.now();
const maxAge = Math.max(...assets.map(a => now - a.lastModified.getTime()));
const assetAge = now - asset.lastModified.getTime();
return maxAge > 0 ? 1 - (assetAge / maxAge) : 1;
};
},
/**
* Weight by tag count (more tags = higher weight)
*/
byTagCount: () => {
return (asset) => {
const tags = asset.metadata.tags || [];
return Array.isArray(tags) ? Math.max(1, tags.length) : 1;
};
},
/**
* Weight by content length
*/
byContentLength: () => {
return (asset, index, assets) => {
if (!asset.content)
return 0.1; // Low weight for assets without content
const maxLength = Math.max(...assets.map(a => a.content?.length || 0));
return maxLength > 0 ? asset.content.length / maxLength : 1;
};
}
};
//# sourceMappingURL=selection-strategies.js.map