@iredium/butterfly
Version:
Express API Framework
576 lines (575 loc) • 28.5 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Butterfly = void 0;
var base_error_1 = require("./errors/base_error");
var event_1 = require("./events/event");
var errors_1 = require("./errors");
var route_drawer_1 = require("./routes/route_drawer");
var mongodb_1 = require("./databases/mongodb");
var middlewares_1 = require("./middlewares");
var express = require("express");
var logger = require("morgan");
var bodyParser = require("body-parser");
var dotenv = require("dotenv");
var path = require("path");
var redis_1 = require("./databases/redis");
var cookieParserGenerator = require("cookie-parser");
var DEFAULT_VIEW_ENGINE = 'pug';
var App = /** @class */ (function () {
function App(config) {
this.booted = false;
this.initCompleted = false;
this.databases = [];
this.hooks = {
'butterfly:setup': [],
'butterfly:ready': [],
'butterfly:registerEventListener': [],
'butterfly:registerViewPaths': [],
'butterfly:registerMiddlewares': [],
'butterfly:drawRoutes': [],
'butterfly:onError': []
};
var routes = config.routes, databases = config.databases, userServiceClass = config.userServiceClass, _a = config.useDefaultLogger, useDefaultLogger = _a === void 0 ? true : _a, _b = config.useViewEngine, useViewEngine = _b === void 0 ? false : _b, _c = config.viewEngine, viewEngine = _c === void 0 ? DEFAULT_VIEW_ENGINE : _c, viewsPaths = config.viewsPaths, errorView = config.errorView, eventListenerMap = config.eventListenerMap;
dotenv.config({
path: path.resolve(process.cwd(), config.env['NODE_ENV'] === 'test' ? '.env.test' : '.env')
});
this.app = express();
this.routes = routes;
this.routeDrawer = route_drawer_1.default;
this.userServiceClass = userServiceClass;
this.useViewEngine = useViewEngine;
this.viewsPaths = viewsPaths || [path.join(process.cwd(), '/views')];
this.errorView = errorView;
this.viewEngine = viewEngine;
this.modules = config.modules || [];
this.useDefaultLogger = useDefaultLogger;
this.eventListenerMap = eventListenerMap || [];
this.databaseConfigs = databases() || {
mongo: {
enable: false,
host: config.env['MONGO_HOST'],
port: config.env['MONGO_PORT'],
database: config.env['MONGO_DATABASE'],
username: config.env['MONGO_USERNAME'],
password: config.env['MONGO_PASSWORD']
}
};
this.app.locals.config = config;
}
App.prototype.init = function () {
return __awaiter(this, void 0, void 0, function () {
var app;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
app = this.app;
if (this.initCompleted)
return [2 /*return*/, app];
console.log('booting modules..');
return [4 /*yield*/, this.bootModules()];
case 1:
_a.sent();
console.log('registering event listeners..');
return [4 /*yield*/, this.registerEventListener()];
case 2:
_a.sent();
console.log('preparing connect instance..');
return [4 /*yield*/, this.setup()];
case 3:
_a.sent();
console.log('connecting to databases..');
return [4 /*yield*/, this.connectDatabases()];
case 4:
_a.sent();
console.log('registering middlewares..');
return [4 /*yield*/, this.registerMiddlewares()];
case 5:
_a.sent();
console.log('drawing routes..');
return [4 /*yield*/, this.drawRoutes()];
case 6:
_a.sent();
console.log('registering error middlewares..');
return [4 /*yield*/, this.registerErrorMiddleware()];
case 7:
_a.sent();
this.initCompleted = true;
console.log('init completed');
return [2 /*return*/, app];
}
});
});
};
App.prototype.boot = function () {
return __awaiter(this, void 0, void 0, function () {
var _a, PORT, app;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (this.booted)
return [2 /*return*/];
_a = this.app.locals.config.env.PORT, PORT = _a === void 0 ? 8080 : _a;
return [4 /*yield*/, this.init()];
case 1:
app = _b.sent();
this.server = app.listen(PORT, function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.executeHookHandlers('butterfly:ready', {
server: this.server,
port: PORT,
})];
case 1:
_a.sent();
return [2 /*return*/];
}
});
}); });
this.booted = true;
return [2 /*return*/];
}
});
});
};
App.prototype.hook = function (name, handler) {
if (this.hooks[name]) {
this.hooks[name].push(handler);
}
};
App.prototype.close = function () {
var _this = this;
return new Promise(function (resolve) {
_this.server.close(function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.closeDatabases()];
case 1:
_a.sent();
resolve();
return [2 /*return*/];
}
});
}); });
});
};
App.prototype.closeDatabases = function () {
return __awaiter(this, void 0, void 0, function () {
var _i, _a, database;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_i = 0, _a = this.databases;
_b.label = 1;
case 1:
if (!(_i < _a.length)) return [3 /*break*/, 4];
database = _a[_i];
return [4 /*yield*/, database.close()];
case 2:
_b.sent();
_b.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/];
}
});
});
};
App.prototype.buildMiddleware = function (middleware) {
return middleware.handleMiddleware({ databases: this.databases });
};
App.prototype.useMiddlewares = function (middlewares) {
var app = this.app;
for (var _i = 0, middlewares_2 = middlewares; _i < middlewares_2.length; _i++) {
var middleware = middlewares_2[_i];
app.use(this.buildMiddleware(middleware));
}
};
App.prototype.bootModules = function () {
return __awaiter(this, void 0, void 0, function () {
var _i, _a, moduleImport, modules, moduleNames, _b, moduleNames_1, moduleName, module_1;
var _this = this;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
_i = 0, _a = this.modules;
_c.label = 1;
case 1:
if (!(_i < _a.length)) return [3 /*break*/, 4];
moduleImport = _a[_i];
return [4 /*yield*/, moduleImport()];
case 2:
modules = _c.sent();
moduleNames = Object.keys(modules);
for (_b = 0, moduleNames_1 = moduleNames; _b < moduleNames_1.length; _b++) {
moduleName = moduleNames_1[_b];
module_1 = modules[moduleName];
if (module_1 && typeof module_1 === 'function') {
module_1({
hook: function (name, handler) {
_this.hook(name, handler);
}
});
}
}
_c.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/];
}
});
});
};
App.prototype.executeHookHandlers = function (name) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
return __awaiter(this, void 0, void 0, function () {
var handlers, _a, handlers_1, handler;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
handlers = this.hooks[name];
if (!handlers)
throw new base_error_1.BaseError('Execute Handler Failed', "there is no hook named \"" + name + "\"");
_a = 0, handlers_1 = handlers;
_b.label = 1;
case 1:
if (!(_a < handlers_1.length)) return [3 /*break*/, 4];
handler = handlers_1[_a];
return [4 /*yield*/, handler.apply(void 0, args)];
case 2:
_b.sent();
_b.label = 3;
case 3:
_a++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/];
}
});
});
};
App.prototype.registerEventListener = function () {
return __awaiter(this, void 0, void 0, function () {
var moduleEventListenerMap, _i, _a, eventListener, eventClassModule, eventClass, _loop_1, _b, _c, listenerModuleImport;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
moduleEventListenerMap = [];
return [4 /*yield*/, this.executeHookHandlers('butterfly:registerEventListener', moduleEventListenerMap)];
case 1:
_d.sent();
_i = 0, _a = __spreadArray(__spreadArray([], moduleEventListenerMap), this.eventListenerMap);
_d.label = 2;
case 2:
if (!(_i < _a.length)) return [3 /*break*/, 8];
eventListener = _a[_i];
return [4 /*yield*/, eventListener.event()];
case 3:
eventClassModule = _d.sent();
eventClass = eventClassModule[Object.keys(eventClassModule)[0]];
_loop_1 = function (listenerModuleImport) {
var listenerClassModule, listenerClassName, listenerClass, event, listener;
return __generator(this, function (_e) {
switch (_e.label) {
case 0: return [4 /*yield*/, listenerModuleImport()];
case 1:
listenerClassModule = _e.sent();
listenerClassName = Object.keys(listenerClassModule)[0];
listenerClass = listenerClassModule[listenerClassName];
event = new eventClass();
listener = new listenerClass();
event_1.Event.on(event.name, function ($event) {
listener.handle($event);
});
return [2 /*return*/];
}
});
};
_b = 0, _c = eventListener.listeners;
_d.label = 4;
case 4:
if (!(_b < _c.length)) return [3 /*break*/, 7];
listenerModuleImport = _c[_b];
return [5 /*yield**/, _loop_1(listenerModuleImport)];
case 5:
_d.sent();
_d.label = 6;
case 6:
_b++;
return [3 /*break*/, 4];
case 7:
_i++;
return [3 /*break*/, 2];
case 8: return [2 /*return*/];
}
});
});
};
App.prototype.setup = function () {
return __awaiter(this, void 0, void 0, function () {
var app;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
app = this.app;
app.use(function (req, _res, next) {
var start = process.hrtime();
if (!req['locals']) {
req['locals'] = {
startTime: start,
timingMark: {}
};
}
next();
});
app.disable('x-powered-by');
if (this.useDefaultLogger) {
app.use(logger('dev', {
skip: function () { return app.get('env') === 'test'; }
}));
}
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParserGenerator());
if (!this.useViewEngine) return [3 /*break*/, 2];
return [4 /*yield*/, this.executeHookHandlers('butterfly:registerViewPaths', this.viewsPaths)];
case 1:
_a.sent();
app.set('view engine', this.viewEngine);
app.set('views', this.viewsPaths);
_a.label = 2;
case 2: return [4 /*yield*/, this.executeHookHandlers('butterfly:setup', app)];
case 3:
_a.sent();
return [2 /*return*/];
}
});
});
};
App.prototype.connectDatabases = function () {
return __awaiter(this, void 0, void 0, function () {
var mongoDb, config, redis, promises, _i, _a, database;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (this.databaseConfigs.mongo.enable) {
mongoDb = new mongodb_1.MongoDb(this.databaseConfigs.mongo, this.app.locals.config.env['NODE_ENV'] !== 'production' && this.app.locals.config.env['NODE_ENV'] !== 'test');
this.databases.push(mongoDb);
}
if (this.databaseConfigs.redis.enable) {
config = this.databaseConfigs.redis;
if (config.password === null)
delete config.password;
redis = new redis_1.Redis(config);
this.databases.push(redis);
}
promises = [];
for (_i = 0, _a = this.databases; _i < _a.length; _i++) {
database = _a[_i];
promises.push(database.connect());
}
return [4 /*yield*/, Promise.all(promises)];
case 1:
_b.sent();
return [2 /*return*/];
}
});
});
};
App.prototype.registerMiddlewares = function () {
return __awaiter(this, void 0, void 0, function () {
var moduleMiddlewares, middlewares;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
moduleMiddlewares = [];
return [4 /*yield*/, this.executeHookHandlers('butterfly:registerMiddlewares', moduleMiddlewares)];
case 1:
_a.sent();
middlewares = __spreadArray([
new middlewares_1.RequestId(),
new middlewares_1.ParseAuthUserMiddleware()
], moduleMiddlewares);
this.useMiddlewares(middlewares);
return [2 /*return*/];
}
});
});
};
App.prototype.registerErrorMiddleware = function () {
return __awaiter(this, void 0, void 0, function () {
var app;
var _this = this;
return __generator(this, function (_a) {
app = this.app;
// Catch 404 and forward to error handler
app.use(function (req, res, next) {
var err = new errors_1.NotFoundError();
res.status(404);
next(err);
});
// Error handler
app.use(function (error, req, res, next) { return __awaiter(_this, void 0, void 0, function () {
var isNotProduction, response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
isNotProduction = req.app.locals.config.env['NODE_ENV'] !== 'production';
response = {
status: 500,
body: {}
};
if (!req['locals']) {
req['locals'] = {};
}
if (error.config) {
response.body = {
config: {
method: error.config.method,
timeout: error.config.timeout,
baseURL: error.config.baseURL,
url: error.config.url,
headers: __assign(__assign({}, (error.config.headers ? error.config.headers : {})), (error.config.headers && error.config.headers.Authorization ? {
Authorization: error.config.headers.Authorization.substr(0, 10) + "***"
} : {})),
data: error.config.data,
},
request: error.request ? {
method: error.request.method,
status: error.request.status,
statusText: error.request.statusText,
withCredentials: error.request.withCredentials,
readyState: error.request.readyState,
responseURL: error.request.responseURL,
timeout: error.request.timeout,
responseType: error.request.responseType,
} : null,
response: error.response ? {
status: error.response.status,
headers: error.response.headers,
data: error.response.data
} : null,
};
}
else if (error.payload) {
response.body = {
name: error.name,
code: error.code,
payload: error.payload
};
if (error.payload.status >= 100 && error.payload.status < 600) {
response.status = error.payload.status;
}
}
response.body['message'] = error.message;
console.error(JSON.stringify({
context: {
user: (req['locals'].user ? {
id: req['locals'].user.id,
username: req['locals'].user.username,
role: req['locals'].user.role
} : null),
req: {
originalUrl: req.originalUrl,
requestId: req['locals'].requestId,
headers: __assign(__assign(__assign({}, req.headers), (req.get('authorization') ? {
authorization: req.get('authorization').substr(0, 12) + '***'
} : {})), (req.get('cookie') ? {
cookie: req.get('cookie').substr(0, 12) + '***'
} : {}))
},
},
error: error.stack
}));
return [4 /*yield*/, this.executeHookHandlers('butterfly:onError', { response: response, error: error, req: req })];
case 1:
_a.sent();
res.status(response.status);
if (this.errorView) {
res.render(this.errorView, { error: error, req: req });
}
else {
res.json(response.body);
}
return [2 /*return*/];
}
});
}); });
return [2 /*return*/];
});
});
};
App.prototype.drawRoutes = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.routeDrawer.draw(this.app, this.routes);
return [4 /*yield*/, this.executeHookHandlers('butterfly:drawRoutes', route_drawer_1.default, this.app)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
return App;
}());
exports.default = App;
exports.Butterfly = App;