reshuffle
Version:
Reshuffle is a fast, unopinionated, minimalist integration framework
177 lines (142 loc) • 4.98 kB
text/typescript
import express, { Express, Request, Response, NextFunction } from 'express'
import { nanoid } from 'nanoid'
import * as availableConnectors from './connectors'
import { MemoryStoreAdapter, PersistentStoreAdapter } from './persistency'
import { BaseConnector, BaseHttpConnector, EventConfiguration } from 'reshuffle-base-connector'
import { createLogger } from './Logger'
import { Logger, LoggerOptions } from 'winston'
import http from 'http'
export interface Handler {
handle: (event?: any) => void
id: string
}
export default class Reshuffle {
availableConnectors: any
httpDelegates: { [path: string]: BaseHttpConnector }
port: number
registry: {
connectors: { [url: string]: BaseConnector }
handlers: { [id: string]: Handler[] }
common: { webserver?: http.Server; persistentStore?: any }
}
logger: Logger
constructor(loggerOptions?: LoggerOptions) {
this.availableConnectors = availableConnectors
this.port = parseInt(<string>process.env.PORT, 10) || 8000
this.httpDelegates = {}
this.registry = { connectors: {}, handlers: {}, common: {} }
this.logger = createLogger(loggerOptions)
this.logger.info('Initializing Reshuffle')
}
prepareWebServer(): Express {
const server = express()
server.route('*').all(async (req: Request, res: Response, next: NextFunction) => {
let handled = false
if (this.httpDelegates[req.params[0]]) {
handled = await this.httpDelegates[req.params[0]].handle(req, res, next)
}
if (!handled) {
res.end(`No handler registered for ${req.url}`)
}
})
return server
}
register(connector: BaseConnector): Reshuffle {
connector.app = this
this.registry.connectors[connector.id] = connector
return this
}
async unregister(connector: BaseConnector): Promise<void> {
await connector.stop()
delete this.registry.connectors[connector.id]
}
getConnector(connectorId: BaseConnector['id']): BaseConnector {
return this.registry.connectors[connectorId]
}
registerHTTPDelegate(path: string, delegate: BaseHttpConnector): Reshuffle {
this.httpDelegates[path] = delegate
return this
}
unregisterHTTPDelegate(path: string): void {
delete this.httpDelegates[path]
}
when(eventConfiguration: EventConfiguration, handler: () => void | Handler): Reshuffle {
const handlerWrapper =
typeof handler === 'object'
? handler
: {
handle: handler,
id: nanoid(),
}
if (this.registry.handlers[eventConfiguration.id]) {
this.registry.handlers[eventConfiguration.id].push(handlerWrapper)
} else {
this.registry.handlers[eventConfiguration.id] = [handlerWrapper]
}
this.logger.info('Registering event ' + eventConfiguration.id)
return this
}
start(port?: number, callback?: () => void): void {
this.port = port || this.port
// Start all connectors
Object.values(this.registry.connectors).forEach((connector) => connector.start(this))
// Start the webserver if we have http delegates
if (Object.keys(this.httpDelegates).length > 0 && !this.registry.common.webserver) {
const express = this.prepareWebServer()
this.registry.common.webserver = express.listen(this.port, () => {
this.logger.info(`Web server listening on port ${this.port}`)
})
}
callback && callback()
}
stopWebServer(): void {
this.registry.common.webserver?.close()
}
restart(port?: number): void {
this.start(port, () => {
this.logger.info('Refreshing Reshuffle configuration')
})
}
async handleEvent(eventId: string, event: any): Promise<boolean> {
if (event == null) {
event = {}
}
const eventHandlers = this.registry.handlers[eventId]
if (eventHandlers.length === 0) {
return false
}
event.getPersistentStore = this.getPersistentStore.bind(this)
event.getConnector = this.getConnector.bind(this)
for (const handler of eventHandlers) {
await this._p_handle(handler, event)
}
return true
}
async _p_handle(handler: Handler, event: any): Promise<void> {
this.logger.defaultMeta = { handlerId: handler.id }
try {
await handler.handle(event)
} catch (error) {
this.logger.error(error.stack)
} finally {
this.logger.defaultMeta = {}
}
}
setPersistentStore(adapter: PersistentStoreAdapter) {
this.registry.common.persistentStore = adapter
return adapter
}
getPersistentStore() {
return this.registry.common.persistentStore || this.setPersistentStore(new MemoryStoreAdapter())
}
public clearInterval(intervalID: NodeJS.Timer): void {
global.clearInterval(intervalID)
}
public setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer {
return global.setInterval(callback, ms, args)
}
public getLogger(): Logger {
return this.logger
}
}
export { Reshuffle }