import {Inject, Singleton} from '../../di' import {CanonicalInstantiable} from '../../service/CanonicalInstantiable' import {Migration} from '../migrations/Migration' import {CanonicalDefinition, CanonicalResolver} from '../../service/Canonical' import {UniversalPath} from '../../util' import {lib} from '../../lib' import {CommandLine} from '../../cli' /** * Service unit that loads and instantiates migration classes. */ @Singleton() export class Migrations extends CanonicalInstantiable { @Inject() protected readonly cli!: CommandLine protected appPath = ['migrations'] protected canonicalItem = 'migration' protected suffix = '.migration' async up(): Promise { if ( await this.path.exists() ) { await super.up() } else { this.logging.debug(`Base migration path does not exist, or has no files: ${this.path}`) } // Register the migrations for @extollo/lib const basePath = lib().concat('migrations') const resolver = await this.buildMigrationNamespaceResolver('@extollo', basePath) this.registerNamespace('@extollo', resolver) } async initCanonicalItem(definition: CanonicalDefinition): Promise { const instance = await super.initCanonicalItem(definition) if ( !(instance instanceof Migration) ) { throw new TypeError(`Invalid migration: ${definition.originalName}. Migrations must extend from @extollo/lib.Migration.`) } instance.setMigrationIdentifier(definition.canonicalName) return instance } /** * Creates a CanonicalResolver for a directory that contains migration files. * * @example * ```ts * const path = universalPath('path', 'to', 'migrations', 'folder') * const namespace = '@mypackage' * * const resolver = await migrations.buildMigrationNamespaceResolver(namespace, path) * migrations.registerNamespace(namespace, resolver) * ``` * @param name * @param basePath */ public async buildMigrationNamespaceResolver(name: string, basePath: UniversalPath): Promise> { if ( !name.startsWith('@') ) { name = `@${name}` } const namespace: {[key: string]: Migration} = {} for await ( const entry of basePath.walk() ) { if ( !this.isValidSuffix(entry) ) { this.logging.debug(`buildMigrationNamespaceResolver - Skipping file with invalid suffix: ${entry}`) continue } const definition = await this.buildCanonicalDefinition(entry, basePath) this.logging.verbose(`buildMigrationNamespaceResolver - Discovered canonical ${this.canonicalItem} "${definition.canonicalName}" from ${entry}`) namespace[definition.canonicalName] = await this.initCanonicalItem(definition) namespace[definition.canonicalName].setMigrationIdentifier(`${name}:${namespace[definition.canonicalName].identifier}`) } return { get: (key: string) => namespace[key], all: () => Object.keys(namespace), } } }