import LifecycleUnit from '../lifecycle/Unit.ts' import {fs, path} from '../external/std.ts' import {Canon} from './Canon.ts' import {Logging} from '../service/logging/Logging.ts' /** * Base type for a canonical definition. */ export interface CanonicalDefinition { canonical_name: string, original_name: string, imported: any, } /** * Base type for a canonical name reference. */ export interface CanonicalReference { resource?: string, item: string, particular?: string, } /** * Base class for all canonical units. Provides helpers for reading and standardizing * the names of classes defined in the filesystem structure. * @extends LifecycleUnit */ export class Canonical extends LifecycleUnit { /** * The base path directory where the canonical definitions reside. * @type string */ protected base_path: string = '.' /** * The file suffix of files in the base path that should be loaded. * @type string */ protected suffix: string = '.ts' /** * The singular, programmatic name of one of these canonical items. * @example middleware * @type string */ protected canonical_item: string = '' /** * Object mapping canonical names to loaded file references. * @type object */ protected _items: { [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 rsc_parts = reference.split('::') const resource = rsc_parts.length > 1 ? rsc_parts[0] + 's' : undefined const rsc_less = rsc_parts.length > 1 ? rsc_parts[1] : rsc_parts[0] const prt_parts = rsc_less.split('.') const item = prt_parts[0] const particular = prt_parts.length > 1 ? prt_parts.slice(1).join('.') : undefined return { resource, item, particular } } /** * Get an array of all canonical reference names. * @return Array */ public all(): string[] { return Object.keys(this._items) } /** * Get the fully-qualified path to the base directory for this unit. * @type string */ public get path(): string { return path.resolve(this.base_path) } /** * Get the plural, programmatic name of the canonical items provide by this unit. * @type string */ public get canonical_items() { return `${this.canonical_item}s` } public async up() { const logger = this.make(Logging) for await ( const entry of fs.walk(this.path) ) { if ( !entry.isFile || !entry.path.endsWith(this.suffix) ) { if ( entry.isFile ) logger.debug(`Skipping file in canonical path with invalid suffix: ${entry.path}`) continue } const def = await this._get_canonical_definition(entry.path) logger.verbose(`Registering canonical ${this.canonical_item} "${def.canonical_name}" from ${entry.path}.`) this._items[def.canonical_name] = await this.init_canonical_item(def) } this.make(Canon).register_canonical(this) } /** * Given a single canonical definition loaded from a file in the base path, * initialize the item and return the result that should be mapped to the reference name. * @param {CanonicalDefinition} definition * @return Promise */ public async init_canonical_item(definition: CanonicalDefinition): Promise { return definition.imported.default } /** * Given a file path, build the canonical definition represented by that path. * @param {string} file_path * @private * @return Promise */ private async _get_canonical_definition(file_path: string): Promise { const original_name = file_path.replace(this.path, '').substr(1) const path_regex = new RegExp(path.SEP, 'g') const canonical_name = original_name.replace(path_regex, ':') .split('').reverse().join('') .substr(this.suffix.length) .split('').reverse().join('') if ( file_path.startsWith('/') ) { file_path = `file://${file_path}` } this.make(Logging).debug(`Importing from: ${file_path}`) const imported = await import(file_path) return { canonical_name, original_name, imported } } /** * Given a canonical reference string, get the corresponding item, if it exists. * @param {string} key * @return any | undefined */ public get(key: string): T | undefined { return this._items[key] } }