/**
 * Copyright (C) Oranda - All Rights Reserved (January 2019 - January 2021)
 */ import { Register } from "./register" ; import { HaliaPlugin } from "./core-plugin" ; export interface Node < T > { id : string ; parents : Node < T > [ ] ; children : Node < T > [ ] ; original : T ; exports ? : any ; isInstalled : boolean ; } interface PluginMap { [ id ] : HaliaPlugin ; } export type ImportRegisterParams < T extends HaliaPlugin > = { stack : HaliaStack < T > ; node : Node < T > ; importMap : { [ id ] : any ; } ; exportMap : { [ id ] : any ; } ; } ; export type ExportRegisterParams < T extends HaliaPlugin > = { stack : HaliaStack < T > ; node : Node < T > ; exportMap : { [ id ] : any ; } ; } ; export type ProcessRegisterParams < T extends HaliaPlugin > = { stack : HaliaStack < T > ; child : Node < T > ; parent : Node < T > ; } ; /**
 * Extension API
 * Defines the HaliaCore "Plugin API" (I also call this an "API-API")
 */ export class HaliaCoreAPI { public importRegister : Register < ImportRegisterParams < any > > = new Register < ImportRegisterParams < any > > ( ) ; public exportRegister : Register < ExportRegisterParams < any > > = new Register < ExportRegisterParams < any > > ( ) ; public processRegister : Register < ProcessRegisterParams < any > > = new Register < ProcessRegisterParams < any > > ( ) ; } export const haliaCoreAPI = new HaliaCoreAPI ( ) ; export class HaliaStack < T extends HaliaPlugin = HaliaPlugin > { private haliaCoreAPI = haliaCoreAPI ; private plugins : PluginMap = { } ; private exports : { [ pluginId ] : any ; } = undefined ; public getPlugin = ( pluginId ) => { return this . plugins [ pluginId ] ; } ; public getExports = ( pluginId ) => { return this . exports [ pluginId ] ; } ; param nodes public nestNodes = async < T > ( nodes ) : Promise < Node < T > [ ] > => { const nodeLog = { } ; const rootNodes = [ ] ; const processNode = async ( node ) => { const { id , parents } = node ; if ( ! nodeLog [ id ] ) { nodeLog [ id ] = true ; for ( const parent of parents ) { await processNode ( parent ) ; parent . children . push ( node ) ; } if ( parents . length == 0 ) { rootNodes . push ( node ) ; } } } ; for ( const node of nodes ) { await processNode ( node ) ; } return rootNodes ; } ; public register = ( plugin ) => { if ( this . plugins [ plugin . id ] != undefined ) { throw `The '${ plugin . name }' plugin has already been installed.` ; } this . plugins [ plugin . id ] = plugin ; } ; private buildNode = ( plugin , nodeCache ) : Node < T > => { const existing = nodeCache [ plugin . id ] ; if ( existing ) { return existing ; } const node = { original : plugin , id : plugin . id , parents : [ ] , children : [ ] , isInstalled : false , } ; const parentPluginIds = plugin . dependencies || [ ] ; const parentNodes = parentPluginIds . map ( ( parentPluginId ) => { const parentPlugin = this . getPlugin ( parentPluginId ) ; const cachedNode = nodeCache [ parentPlugin . id ] ; if ( cachedNode ) { return cachedNode ; } const parentNode = this . buildNode ( parentPlugin as any , nodeCache ) ; parentNode . children . push ( node ) ; return parentNode ; } ) ; node . parents = parentNodes ; nodeCache [ node . id ] = node ; return node ; } ; private async processNode ( node , exportMap ) { const { original : plugin , children } = node ; const imports = this . getNodeExports ( node , exportMap ) ; let exports ; try { exports = await plugin . install ( imports ) ; } catch ( err ) { throw `An error occurred while installing the '${ plugin . name }' Halia Plugin: ${ JSON . stringify ( err ) }` ; } node . isInstalled = true ; this . setNodeExports ( node , exports , exportMap ) ; node . exports = exports ; for ( const child of children ) { if ( child . isInstalled ) { continue ; } const isPending = child . parents . map ( ( parent ) => parent . isInstalled ) . includes ( false ) ; if ( isPending ) { continue ; } await this . processNode ( child , exportMap ) ; this . haliaCoreAPI . processRegister . invoke ( { stack : this , child , parent : node } ) ; } } private getNodeExports = ( node , exportMap ) => { const { original : { dependencies = [ ] } } = node ; const importMap = { } ; dependencies . forEach ( ( depId ) => { importMap [ depId ] = exportMap [ depId ] ; } ) ; this . haliaCoreAPI . importRegister . invoke ( { stack : this , node , importMap , exportMap } ) ; return importMap ; } ; private setNodeExports = ( node , value , exportMap ) => { const { original : { id } } = node ; if ( exportMap [ id ] != undefined ) { throw `An export has already been defined for the '${ id }' Plugin.` ; } exportMap [ id ] = value ; this . haliaCoreAPI . exportRegister . invoke ( { stack : this , node , exportMap } ) ; } ; /**
     * Builds the Halia Stack and returns the root nodes.
     */ public build = async ( ) => { const plugins = Object . keys ( this . plugins ) . map ( ( pluginName ) => this . plugins [ pluginName ] ) ; const nodeCache = { } ; const nodes = plugins . map ( ( plugin ) => this . buildNode ( plugin as any , nodeCache ) ) ; const rootNodes = await this . nestNodes ( nodes ) ; const exportMap = { } ; for ( const node of rootNodes ) { await this . processNode ( node , exportMap ) ; } this . exports = exportMap ; return rootNodes ; } ; }