UNPKG

flownote

Version:

FlowNote lets developers create, organize, and reason about event-oriented applications with a simple flow-based language.

207 lines (176 loc) 6.45 kB
import Event from './event' import Spider from './spider' import ActionContext from './actionContext' import MemoryQueue from './queues/memoryQueue' const CommonClass = require('./utils/commonClass') const Log = require('./utils/log') const queueTypes = new Map() queueTypes.set('memory', MemoryQueue) // const numCPUs = require('os').cpus().length /** * Every Application instance has its own EventQueue. */ class EventQueue extends CommonClass { /** * [constructor description] * @return {[type]} [description] */ constructor (application, queue) { super() this.application = application if (queue !== undefined) { this.fromJSON({ queue: queue }) } } /** * [push description] * @param {[type]} source [description] * @param {[type]} eventName [description] * @param {[type]} event [description] * @param {[type]} destination [description] * @return {[type]} [description] */ push (event, enqueue = true) { this.log.debug('Pushing event to queue...') this.queue.push(event) if (enqueue) { this.log.debug('Scheduling event queue processing...') /* return new Promise(resolve => { resolve(this.process()) }) /* Promise.resolve().then(() => { return this.process() }) */ setImmediate(() => { return this.process() }) } } /** * [process description] * @return {[type]} [description] */ async process () { this.log.debug('Processing event queue...') const remaining = this.queue.count() if (remaining > 0) { this.log.debug(`Event queue has ${remaining} events remaining...`) const event = this.queue.pop() if (event && event instanceof Event) { this.log.debug(`Processing event ${event.type} -> with target: ${event.from.name}`) if (event.from && event.from.process) { this.log.debug(`Preparing Action Context from ${event.from.name}`) const actionContext = new ActionContext(this.application, event.flow, event.from, event.request) this.log.debug('Processing event step...') try { await event.from.process(event, actionContext) event.request.addStep(this.application, event.flow, event.from) } catch (e) { this.log.debug('Caught error...') // An error has occured, go get the last step let retryCount = event.from.retry || 0 let retryDelay = event.from.retryDelay || 0 let lastStep if (event.from.accepts === undefined) { // Dealing with a step, not a channel const previousStep = event.request.steps[event.request.steps.length - 1] if (previousStep && this.application.id === previousStep.appId) { const lastFlow = this.application.getFlow(previousStep.flowId) if (lastFlow) { lastStep = new Spider().search(lastFlow, previousStep.stepId) if (lastStep && lastStep.retry) { retryCount = lastStep.retry retryDelay = lastStep.retryDelay } } } } if (typeof retryCount === 'string') { // Retry is an Action, not a number const action = this.application.getAction(retryCount) if (!action) { throw new Error(`${retryCount} isn't a retry action for ${event.from.name} node`) } retryCount = await action.execute(actionContext) } if (typeof retryDelay === 'string') { // Retry is an Action, not a number const action = this.application.getAction(retryDelay) if (!action) { throw new Error(`${retryDelay} isn't a rety action for ${event.from.name} node`) } retryDelay = await action.execute(actionContext) } // The step has retry instructions if (event.retries <= retryCount - 1) { if (retryDelay > 0) { setTimeout(() => { this.log.debug('Retrying...') this.application.dispatch('RetryChannel', event.request, event.flow, lastStep || event.from, event.retries + 1) }, retryDelay) } else { this.log.debug('Retrying...') this.application.dispatch('RetryChannel', event.request, event.flow, lastStep || event.from, event.retries + 1) } } else { // No more retries, bail this.log.debug('Dispatching error...', e.name) this.application.dispatch(e.name, event.request, event.flow, event.from, event.retries, e) } } return event.request } else { throw new Error('Event.from does not have a process() method.') } } else { throw new Error('Event in Event Queue is not an Event class.') } } } /** * [registerQueueTypes description] * @param {[type]} name [description] * @param {[type]} queueType [description] * @return {[type]} [description] */ registerQueueTypes (name, queueType) { queueTypes.set(name, queueType) } /** * [toJSON description] * @return {[type]} [description] */ toJSON () { return { queue: this.queue } } /** * [fromJSON description] * @param {[type]} json [description] * @return {[type]} [description] */ fromJSON (json) { let result if (typeof json === 'string') { result = this.loadFlattened(json) } else if (json instanceof Object && !(json instanceof Array)) { result = json } else { throw new Error(`Expected Event JSON to be a string or an object, but got a ${typeof json} instead`) } this.log = new Log(this.application.id, 'EventQueue', this.application.name, this.application.config.logLevel, this.application.outputPipe, this.application.errorPipe) const QueueType = queueTypes.get(result.queue.type) if (!QueueType) { throw new Error(`Unknown queue type ${result.queueType}`) } this.queue = new QueueType(this.application, result.queue.pendingEvents) return this } } module.exports = EventQueue