@deepbrainspace/nx-surrealdb
Version:
NX plugin for SurrealDB migrations with modular architecture
207 lines • 9.29 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MigrationFileProcessor = void 0;
const tslib_1 = require("tslib");
const fs = tslib_1.__importStar(require("fs/promises"));
const path = tslib_1.__importStar(require("path"));
const env_1 = require("../infrastructure/env");
class MigrationFileProcessor {
static async findMatchingSubdirectory(basePath, pattern) {
try {
const subDirs = await fs.readdir(basePath, { withFileTypes: true });
const directories = subDirs.filter(d => d.isDirectory()).map(d => d.name);
const normalizedPattern = pattern.trim().toLowerCase();
const patternAsNumber = parseInt(normalizedPattern, 10);
const normalizedPatternNumber = isNaN(patternAsNumber) ? null : patternAsNumber.toString();
for (const dirName of directories) {
const normalizedDirName = dirName.toLowerCase();
const match = normalizedDirName.match(this.SUBDIR_PATTERN);
if (!match)
continue;
const [, number, name] = match;
const normalizedNumber = parseInt(number, 10).toString();
if (normalizedDirName === normalizedPattern ||
(normalizedPatternNumber !== null && normalizedPatternNumber === normalizedNumber) ||
normalizedPattern === name.toLowerCase() ||
normalizedPattern === `${normalizedNumber}_${name.toLowerCase()}` ||
normalizedPattern === `${number}_${name.toLowerCase()}`) {
return dirName;
}
}
return null;
}
catch (error) {
throw new Error(`Failed to read subdirectories in ${basePath}: ${error.message}`);
}
}
static async discoverModules(basePath) {
try {
const subDirs = await fs.readdir(basePath, { withFileTypes: true });
const modules = [];
for (const dirent of subDirs) {
if (dirent.isDirectory() && this.SUBDIR_PATTERN.test(dirent.name)) {
modules.push({
moduleId: dirent.name,
modulePath: path.join(basePath, dirent.name),
});
}
}
// Sort by module number
return modules.sort((a, b) => {
const aMatch = a.moduleId.match(this.SUBDIR_PATTERN);
const bMatch = b.moduleId.match(this.SUBDIR_PATTERN);
if (!aMatch || !bMatch)
return 0;
return parseInt(aMatch[1], 10) - parseInt(bMatch[1], 10);
});
}
catch (error) {
if (error.code === 'ENOENT') {
return [];
}
throw new Error(`Failed to discover modules in ${basePath}: ${error.message}`);
}
}
static async getNextMigrationNumber(modulePath) {
try {
const files = await fs.readdir(modulePath);
const migrationFiles = files.filter(f => this.MIGRATION_PATTERN.test(f));
if (migrationFiles.length === 0) {
return '0001';
}
// Find the highest migration number
let maxNumber = 0;
for (const file of migrationFiles) {
const match = file.match(this.MIGRATION_PATTERN);
if (match) {
const number = parseInt(match[1], 10);
if (number > maxNumber) {
maxNumber = number;
}
}
}
return String(maxNumber + 1).padStart(4, '0');
}
catch (error) {
if (error.code === 'ENOENT') {
return '0001';
}
throw new Error(`Failed to get next migration number for ${modulePath}: ${error.message}`);
}
}
static async getAllMigrationFiles(basePath) {
const migrationFiles = [];
try {
const modules = await this.discoverModules(basePath);
for (const module of modules) {
const moduleFiles = await fs.readdir(module.modulePath);
for (const file of moduleFiles) {
if (file.endsWith('.surql')) {
// Return relative path from basePath (module/file.surql)
migrationFiles.push(path.join(module.moduleId, file));
}
}
}
}
catch (error) {
// If directory doesn't exist, return empty array
if (error.code === 'ENOENT') {
return [];
}
throw error;
}
return migrationFiles.sort();
}
static generateModuleId(name, existingModules) {
// Find the next available module number with gapped numbering
const existingNumbers = existingModules
.map(m => {
const match = m.moduleId.match(this.SUBDIR_PATTERN);
return match ? parseInt(match[1], 10) : -1;
})
.filter(n => n >= 0)
.sort((a, b) => a - b);
// Use gapped numbering: 000, 010, 020, 030, etc.
// Find the next number after the highest existing number
let nextNumber = 0;
if (existingNumbers.length > 0) {
const highestNumber = existingNumbers[existingNumbers.length - 1];
nextNumber = highestNumber + 10;
}
const normalizedName = name.toLowerCase().replace(/[^a-z0-9]/g, '_');
return `${String(nextNumber).padStart(3, '0')}_${normalizedName}`;
}
static parseMigrationFile(filename, basePath, moduleId) {
const match = filename.match(this.MIGRATION_PATTERN);
if (!match)
return null;
const [, number, name, direction] = match;
const filePath = basePath ? path.join(basePath, filename) : filename;
return {
number,
name,
direction: direction,
filename,
filePath,
moduleId: moduleId || '',
content: '',
checksum: '',
};
}
static matchesMigrationPattern(filename, pattern) {
const migration = this.parseMigrationFile(filename);
if (!migration)
return false;
const normalizedPattern = pattern.trim().toLowerCase().replace(/^0+/, '');
const normalizedNumber = parseInt(migration.number, 10).toString();
return (filename.toLowerCase() === pattern.toLowerCase() ||
filename.toLowerCase() === `${pattern.toLowerCase()}.surql` ||
normalizedPattern === normalizedNumber ||
normalizedPattern === migration.name.toLowerCase() ||
normalizedPattern === `${normalizedNumber}_${migration.name.toLowerCase()}` ||
normalizedPattern === `${migration.number}_${migration.name.toLowerCase()}`);
}
static filterMigrationFiles(files, filePattern, direction = 'up') {
let filtered = files.filter(f => f.endsWith(`_${direction}.surql`));
if (filePattern) {
filtered = filtered.filter(f => this.matchesMigrationPattern(f, filePattern));
}
return direction === 'down' ? filtered.reverse() : filtered.sort();
}
static processContent(content, context) {
const processed = (0, env_1.replaceEnvVars)(content);
const statements = [];
const hasNamespaceOperation = this.NAMESPACE_OPERATIONS.test(processed);
const hasDatabaseOperation = this.DATABASE_OPERATIONS.test(processed);
const hasDDLOperation = this.DDL_OPERATIONS.test(processed);
const hasBeginTransaction = this.TRANSACTION_BEGIN.test(processed);
const hasCommitTransaction = this.TRANSACTION_COMMIT.test(processed);
if (!hasNamespaceOperation && context.defaultNamespace) {
statements.push(`USE NAMESPACE ${context.defaultNamespace};`);
}
if (!hasDatabaseOperation && context.defaultDatabase) {
statements.push(`USE DATABASE ${context.defaultDatabase};`);
}
if (hasDDLOperation ||
!context.useTransactions ||
hasBeginTransaction ||
hasCommitTransaction) {
statements.push(processed);
}
else {
statements.push('BEGIN TRANSACTION;');
statements.push(processed);
statements.push('COMMIT TRANSACTION;');
}
return statements.join('\n\n');
}
}
exports.MigrationFileProcessor = MigrationFileProcessor;
MigrationFileProcessor.MIGRATION_PATTERN = /^(\d{4})_(.+?)_(up|down)\.surql$/;
MigrationFileProcessor.SUBDIR_PATTERN = /^(\d{1,4})_(.+)$/;
MigrationFileProcessor.NAMESPACE_OPERATIONS = /(?:DEFINE|USE|REMOVE)\s+NAMESPACE/i;
MigrationFileProcessor.DATABASE_OPERATIONS = /(?:DEFINE|USE|REMOVE)\s+DATABASE/i;
MigrationFileProcessor.DDL_OPERATIONS = /^\s*(DEFINE|REMOVE)\s+(NAMESPACE|DATABASE|TABLE|FIELD|INDEX|FUNCTION)/im;
MigrationFileProcessor.TRANSACTION_BEGIN = /BEGIN\s+TRANSACTION/i;
MigrationFileProcessor.TRANSACTION_COMMIT = /COMMIT\s+TRANSACTION/i;
//# sourceMappingURL=migration-file-processor.js.map