UNPKG

@freemework/sql.misc.migration

Version:

Hosting library of the Freemework Project.

280 lines 13.6 kB
import * as fs from "fs"; import * as path from "path"; import { promisify } from "util"; import { fileURLToPath } from "url"; import { FCancellationExecutionContext, FExceptionArgument, FExceptionInvalidOperation } from "@freemework/common"; const existsAsync = promisify(fs.exists); const mkdirAsync = promisify(fs.mkdir); const readdirAsync = promisify(fs.readdir); const readFileAsync = promisify(fs.readFile); const writeFileAsync = promisify(fs.writeFile); export class FSqlMigrationSources { versionNames; _versions; /** * Load data into memory and represent it as FMigrationSources * @param executionContext * @param sourceUri Sources url. Support schemas `file:`, `http+tar+gz:` and `https+tar+gz:` */ static load(executionContext, sourceUri, opts) { switch (sourceUri.protocol) { case UrlSchemas.FILE: { const sourceDirectory = fileURLToPath(sourceUri); return FSqlMigrationSources.loadFromFilesystem(executionContext, sourceDirectory, opts); } case UrlSchemas.HTTP_TAR_GZ: throw new FExceptionInvalidOperation("Not implemented yet"); case UrlSchemas.HTTPS_TAR_GZ: throw new FExceptionInvalidOperation("Not implemented yet"); default: throw new NotSupportedUrlSchemaException(sourceUri); } } static async loadFromFilesystem(executionContext, sourceDirectory, opts) { if (!await existsAsync(sourceDirectory)) { throw new FSqlMigrationSources.WrongMigrationDataException(`Migration directory '${sourceDirectory}' is not exist`); } const migrationBundles = []; const listVersions = (await readdirAsync(sourceDirectory, { withFileTypes: true })) .filter(w => w.isDirectory()) .map(directory => directory.name); const cancellationToken = FCancellationExecutionContext.of(executionContext).cancellationToken; if (listVersions.length > 0) { for (const version of listVersions) { if (opts !== undefined) { // Control version load range, skip out range versions if (opts.versionFrom !== undefined) { if (version < opts.versionFrom) { continue; } } if (opts.versionTo !== undefined) { if (version > opts.versionTo) { continue; } } } cancellationToken.throwIfCancellationRequested(); const versionDirectory = path.join(sourceDirectory, version); const installDirectory = path.join(versionDirectory, FSqlMigrationSources.Direction.INSTALL); const rollbackDirectory = path.join(versionDirectory, FSqlMigrationSources.Direction.ROLLBACK); const installBundleItems = []; const rollbackBundleItems = []; if (await existsAsync(installDirectory)) { const migrationFiles = await readdirAsync(installDirectory); if (migrationFiles.length > 0) { for (const migrationFile of migrationFiles) { cancellationToken.throwIfCancellationRequested(); const scriptFile = path.join(installDirectory, migrationFile); const scriptContent = await readFileAsync(scriptFile, "utf-8"); const scriptKind = resolveScriptKindByExtension(scriptFile); installBundleItems.push(new FSqlMigrationSources.Script(migrationFile, scriptKind, scriptFile, scriptContent)); } } } if (await existsAsync(rollbackDirectory)) { const migrationFiles = await readdirAsync(rollbackDirectory); if (migrationFiles.length > 0) { for (const migrationFile of migrationFiles) { cancellationToken.throwIfCancellationRequested(); const scriptFile = path.join(rollbackDirectory, migrationFile); const scriptContent = await readFileAsync(scriptFile, "utf-8"); const scriptKind = resolveScriptKindByExtension(scriptFile); rollbackBundleItems.push(new FSqlMigrationSources.Script(migrationFile, scriptKind, scriptFile, scriptContent)); } } } migrationBundles.push(new FSqlMigrationSources.VersionBundle(version, installBundleItems, rollbackBundleItems)); } } return new FSqlMigrationSources(migrationBundles); } getVersionBundle(versionName) { const item = this._versions.get(versionName); if (item === undefined) { throw new FExceptionArgument(`No version bundle with name: ${versionName}`, "versionName"); } return item; } map(callbackfn) { const mappedBundles = []; for (const versionName of this.versionNames) { const bundle = this.getVersionBundle(versionName); const newBundle = bundle.map((item, opts) => { return callbackfn(item.content, Object.freeze({ itemName: item.name, direction: opts.direction, versionName })); }); mappedBundles.push(newBundle); } return new FSqlMigrationSources(mappedBundles); } async saveToFilesystem(executionContext, destinationDirectory) { if (!(await existsAsync(destinationDirectory))) { throw new FExceptionArgument(`Target directory '${destinationDirectory}' not exist. You must provide empty directory.`, "destinationDirectory"); } const cancellationToken = FCancellationExecutionContext.of(executionContext).cancellationToken; for (const versionName of this.versionNames) { cancellationToken.throwIfCancellationRequested(); const versionDirectory = path.join(destinationDirectory, versionName); const installDirectory = path.join(versionDirectory, FSqlMigrationSources.Direction.INSTALL); const rollbackDirectory = path.join(versionDirectory, FSqlMigrationSources.Direction.ROLLBACK); await mkdirAsync(versionDirectory); cancellationToken.throwIfCancellationRequested(); await mkdirAsync(installDirectory); cancellationToken.throwIfCancellationRequested(); await mkdirAsync(rollbackDirectory); const versionBundle = this.getVersionBundle(versionName); for (const installItemName of versionBundle.installScriptNames) { cancellationToken.throwIfCancellationRequested(); const bundleFile = path.join(installDirectory, installItemName); const bundleItem = versionBundle.getInstallScript(installItemName); await writeFileAsync(bundleFile, bundleItem.content, "utf-8"); } for (const rollbackItemName of versionBundle.rollbackScriptNames) { cancellationToken.throwIfCancellationRequested(); const bundleFile = path.join(rollbackDirectory, rollbackItemName); const bundleItem = versionBundle.getRollbackScript(rollbackItemName); await writeFileAsync(bundleFile, bundleItem.content, "utf-8"); } } } constructor(bundles) { this._versions = new Map(bundles.map(bundle => ([bundle.versionName, bundle]))); this.versionNames = Object.freeze([...this._versions.keys()].sort()); } } (function (FSqlMigrationSources) { class WrongMigrationDataException extends FExceptionInvalidOperation { } FSqlMigrationSources.WrongMigrationDataException = WrongMigrationDataException; class VersionBundle { versionName; installScriptNames; rollbackScriptNames; _installScripts; _rollbackScripts; constructor(versionName, installItems, rollbackItems) { this.versionName = versionName; this._installScripts = new Map(installItems.map(installItem => ([installItem.name, installItem]))); this._rollbackScripts = new Map(rollbackItems.map(rollbackItem => ([rollbackItem.name, rollbackItem]))); this.installScriptNames = Object.freeze([...this._installScripts.keys()].sort()); this.rollbackScriptNames = Object.freeze([...this._rollbackScripts.keys()].sort()); } getInstallScript(itemName) { const item = this._installScripts.get(itemName); if (item === undefined) { throw new FExceptionArgument(`No bundle item with name: ${itemName}`, "itemName"); } return item; } getRollbackScript(itemName) { const script = this._rollbackScripts.get(itemName); if (script === undefined) { throw new FExceptionArgument(`No bundle item with name: ${itemName}`, "itemName"); } return script; } map(callbackFn) { const installScripts = VersionBundle._map(this.installScriptNames, this._installScripts, (item) => { return callbackFn(item, { direction: FSqlMigrationSources.Direction.INSTALL }); }); const rollbackScripts = VersionBundle._map(this.rollbackScriptNames, this._rollbackScripts, (item) => { return callbackFn(item, { direction: FSqlMigrationSources.Direction.ROLLBACK }); }); return new VersionBundle(this.versionName, installScripts, rollbackScripts); } static _map(itemNames, itemsMap, callbackfn) { const mappedItems = []; for (const itemName of itemNames) { const item = itemsMap.get(itemName); const newContent = callbackfn(item); mappedItems.push(new Script(item.name, item.kind, item.file, newContent)); } return mappedItems; } } FSqlMigrationSources.VersionBundle = VersionBundle; class Script { name; kind; file; content; constructor(name, kind, file, content) { this.name = name; this.kind = kind; this.file = file; this.content = content; } } FSqlMigrationSources.Script = Script; (function (Script) { let Kind; (function (Kind) { Kind["SQL"] = "SQL"; Kind["JAVASCRIPT"] = "JAVASCRIPT"; Kind["UNKNOWN"] = "UNKNOWN"; })(Kind = Script.Kind || (Script.Kind = {})); (function (Kind) { function guard(kind) { const friendlyValue = kind; switch (friendlyValue) { case Kind.SQL: case Kind.JAVASCRIPT: case Kind.UNKNOWN: return true; default: return guardFalse(friendlyValue); } } Kind.guard = guard; // tslint:disable-next-line: no-shadowed-variable function parse(kind) { const friendlyValue = kind; if (guard(friendlyValue)) { return friendlyValue; } throw new UnreachableNotSupportedScriptKindException(friendlyValue); } Kind.parse = parse; class UnreachableNotSupportedScriptKindException extends FExceptionArgument { constructor(kind) { super(`Not supported script kind '${JSON.stringify(kind)}'`, "fiatCurrency"); } } Kind.UnreachableNotSupportedScriptKindException = UnreachableNotSupportedScriptKindException; function guardFalse(_never) { return false; } })(Kind = Script.Kind || (Script.Kind = {})); })(Script = FSqlMigrationSources.Script || (FSqlMigrationSources.Script = {})); let Direction; (function (Direction) { Direction["INSTALL"] = "install"; Direction["ROLLBACK"] = "rollback"; })(Direction = FSqlMigrationSources.Direction || (FSqlMigrationSources.Direction = {})); })(FSqlMigrationSources || (FSqlMigrationSources = {})); var UrlSchemas; (function (UrlSchemas) { UrlSchemas["FILE"] = "file:"; UrlSchemas["HTTP_TAR_GZ"] = "http+tar+gz:"; UrlSchemas["HTTPS_TAR_GZ"] = "https+tar+gz:"; })(UrlSchemas || (UrlSchemas = {})); class NotSupportedUrlSchemaException extends FExceptionInvalidOperation { constructor(uri) { super(`Not supported schema: ${uri}`); } } const sqlFilesExtensions = Object.freeze([".sql"]); const jsFilesExtensions = Object.freeze([".js", ".javascript"]); function resolveScriptKindByExtension(fileName) { const ext = path.extname(fileName); if (sqlFilesExtensions.includes(ext)) { return FSqlMigrationSources.Script.Kind.SQL; } if (jsFilesExtensions.includes(ext)) { return FSqlMigrationSources.Script.Kind.JAVASCRIPT; } return FSqlMigrationSources.Script.Kind.UNKNOWN; } //# sourceMappingURL=f_sql_migration_sources.js.map