/** * Base type for a canonical definition. */ import {Canon} from "./Canon"; import {universalPath, UniversalPath} from "@extollo/util"; import {Logging} from "./Logging"; import {Inject} from "@extollo/di"; import * as nodePath from 'path' import {Unit} from "../lifecycle/Unit"; export interface CanonicalDefinition { canonicalName: string, originalName: string, imported: any, } /** * Base type for a canonical name reference. */ export interface CanonicalReference { resource?: string, item: string, particular?: string, } export abstract class Canonical extends Unit { @Inject() protected readonly logging!: Logging @Inject() protected readonly canon!: Canon /** * The base path directory where the canonical definitions reside. * @type string */ protected appPath: string[] = [] /** * The file suffix of files in the base path that should be loaded. * @type string */ protected suffix: string = '.js' /** * The singular, programmatic name of one of these canonical items. * @example middleware * @type string */ protected canonicalItem: string = '' /** * Object mapping canonical names to loaded file references. * @type object */ protected loadedItems: { [key: string]: T } = {} /** * Resolve a canonical reference from its string form to a CanonicalReference. * @param {string} reference * @return CanonicalReference */ public static resolve(reference: string): CanonicalReference { const rscParts = reference.split('::') const resource = rscParts.length > 1 ? rscParts[0] + 's' : undefined const rsc_less = rscParts.length > 1 ? rscParts[1] : rscParts[0] const prtParts = rsc_less.split('.') const item = prtParts[0] const particular = prtParts.length > 1 ? prtParts.slice(1).join('.') : undefined return { resource, item, particular } } public all(): string[] { return Object.keys(this.loadedItems) } public get path(): UniversalPath { return this.app().appPath(...this.appPath) } public get canonicalItems() { return `${this.canonicalItem}s` } public get(key: string): T | undefined { return this.loadedItems[key] } public async up() { for await ( const entry of this.path.walk() ) { if ( !entry.endsWith(this.suffix) ) { this.logging.debug(`Skipping file with invalid suffix: ${entry}`) continue } const definition = await this.buildCanonicalDefinition(entry) this.logging.verbose(`Registering canonical ${this.canonicalItem} "${definition.canonicalName}" from ${entry}`) this.loadedItems[definition.canonicalName] = await this.initCanonicalItem(definition) } this.canon.registerCanonical(this) } public async initCanonicalItem(definition: CanonicalDefinition): Promise { return definition.imported.default } protected async buildCanonicalDefinition(filePath: string): Promise { const originalName = filePath.replace(this.path.toLocal, '').substr(1) const pathRegex = new RegExp(nodePath.sep, 'g') const canonicalName = originalName.replace(pathRegex, ':') .split('').reverse().join('') .substr(this.suffix.length) .split('').reverse().join('') const fullUniversalPath = universalPath(filePath) this.logging.verbose(`Importing from: ${fullUniversalPath}`) const imported = await import(fullUniversalPath.toLocal) return { canonicalName, originalName, imported } } }