UNPKG

cloudworker-proxy

Version:
128 lines (101 loc) 4.29 kB
const path = require('path') const Context = require('./Context') const { fileExists } = require('./utils') class Component { constructor(id, context) { this.id = id || this.constructor.name if (this.id === 'Context') { throw Error('You cannot use "Context" as a component name. It is reserved.') } // todo change this if (!context || !context.readState) { context = new Context(context) } // we need to keep the entire instance in memory to pass it to child components this.context = { instance: context } // Set state this.state = {} // Define default function // Adds the instance context to it const that = this // make sure author defined at least a default function if (typeof that.default !== 'function') { throw Error(`default function is missing for component "${this.id}"`) } const defaultFunction = function(inputs) { return that.default.call(that, inputs) } // Add Component class properties like context and state Object.keys(this).forEach((prop) => { defaultFunction[prop] = this[prop] }) // Add Component class methods like the save() method const classMethods = Object.getOwnPropertyNames(Component.prototype) classMethods.forEach((classMethod) => { defaultFunction[classMethod] = (...classMethodInputs) => this[classMethod].call(that, ...classMethodInputs) // apply instance context }) // Add instance methods // those are the methods of the class that extends Component // if user added his own save() method for example, // this would overwrite the Component class save() method const instanceMethods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)) instanceMethods.forEach((instanceMethod) => { defaultFunction[instanceMethod] = (instanceMethodInputs) => this[instanceMethod].call(that, instanceMethodInputs) // apply instance context }) return defaultFunction } // populating state is an async operation in most contexts // and we can't run async operations in the constructor // so we can't auto populate state on instance construction async init() { await this.context.instance.init() this.state = await this.context.instance.readState(this.id) // the context object in the componnet instance is a subset // of the actual context instance, to keep the component // api clean, and be able to turn it off for child components this.context.credentials = this.context.instance.credentials this.context.resourceId = () => this.context.instance.resourceId() this.context.log = (msg) => this.context.instance.log(msg) this.context.debug = (msg) => this.context.instance.debug(msg) this.context.status = (msg, entity) => this.context.instance.status(msg, entity || this.id) // keeping this function for a while this.context.output = (key, val) => { if (typeof this.context.instance.output === 'function') { return this.context.instance.output(key, val) } return } } async save() { await this.context.instance.writeState(this.id, this.state) } async load(nameOrPath, componentAlias) { let externalComponentPath let childComponent if (this.context.instance.root) { externalComponentPath = path.resolve(this.context.instance.root, nameOrPath, 'serverless.js') } else { externalComponentPath = path.resolve(nameOrPath, 'serverless.js') } if (await fileExists(externalComponentPath)) { delete require.cache[require.resolve(externalComponentPath)] childComponent = require(externalComponentPath) } else { childComponent = require(nameOrPath) } const childComponentId = `${this.id}.${componentAlias || childComponent.name}` const childComponentInstance = new childComponent(childComponentId, this.context.instance) // populate state based on the component id await childComponentInstance.init() // disable context methods for child components childComponentInstance.context.log = () => {} childComponentInstance.context.status = () => {} childComponentInstance.context.output = () => {} return childComponentInstance } } module.exports = Component