lemon-core
Version:
Lemon Serverless Micro-Service Platform
239 lines • 9.85 kB
JavaScript
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LambdaHandler = exports.buildReportError = exports.LambdaSubHandler = void 0;
/**
* `lambda-handler.ts`
* - main lambda handler.
*
*
* @author Steve Jung <steve@lemoncloud.io>
* @date 2019-11-20 initial version via backbone
*
* @copyright (C) 2019 LemonCloud Co Ltd. - All Rights Reserved.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const engine_1 = require("../../engine/");
const test_helper_1 = require("../../common/test-helper");
const NS = engine_1.$U.NS('LMDA', 'green'); // NAMESPACE TO BE PRINTED.
class LambdaSubHandler {
constructor(lambda, type) {
if (!lambda)
throw new Error('@lambda (lambda-handler) is required!');
this.lambda = lambda;
this.type = type;
if (lambda && type)
lambda.setHandler(type, this);
}
}
exports.LambdaSubHandler = LambdaSubHandler;
/**
* build reprot-error function in safe.
*
* @param isReport flag to report-error via sns
* @return the last error message
*/
const buildReportError = (isReport) => (e, context, event, data) => {
return (isReport ? (0, engine_1.doReportError)(e, context, event, data) : Promise.resolve(data))
.then(() => (0, test_helper_1.GETERR)(e))
.catch(test_helper_1.GETERR);
};
exports.buildReportError = buildReportError;
/**
* class: `LambdaHandler`
* - general lambda handler so that routes to proper target handlers.
*/
class LambdaHandler {
//* protected constructor.
constructor(config) {
//* handler map.
this._map = {};
//* Find Service By Event
this.findService = (event) => {
const headers = (event && event.headers) || {};
(0, engine_1._log)(NS, `> headers =`, engine_1.$U.json(headers));
//* check if AWS SNS Notification Subscription -> notification controller.
if (event.requestContext &&
headers['x-amz-sns-message-type'] &&
headers['x-amz-sns-message-id'] &&
headers['x-amz-sns-topic-arn']) {
//* via HTTP/HTTPS SNS
return 'notification';
}
else if (event.requestContext && event.pathParameters !== undefined) {
//* via ApiGateway
return 'web';
}
else if (event.requestContext && event.requestContext.eventType !== undefined) {
//* via WEB-SOCKET from ApiGateway
return 'wss';
}
else {
if (event.cron) {
//* via CloudWatch's cron.
return 'cron';
}
else if (event.userPoolId) {
//* via cognito event
return 'cognito';
}
else if (event.Records) {
//* decode `Records` to find target.
const records = Array.isArray(event.Records) ? event.Records : [];
const sns = records.filter((_) => (_.Sns ? true : false)); // via sns event.
const sqs = records.filter((_) => _.eventSource == 'aws:sqs'); // via sqs data/
const ddb = records.filter((_) => (_.dynamodb ? true : false)); // via dynamodb
if (sns.length)
return 'sns';
if (sqs.length)
return 'sqs';
if (ddb.length)
return 'dds';
}
}
};
this.config = config;
}
/**
* set service lambda handler.
* @param type name of type
* @param handler handler of service
*/
setHandler(type, handler) {
let key = `${type || ''}`.toLowerCase().trim();
key = key === 'dynamo-stream' ? 'dds' : key;
// console.info(`! set-handler[${type}] =`, typeof handler);
if (key)
this._map[key] = handler;
}
/**
* decode event to proper handler.
* - NOTE! - returns promised results with `async`
*
* @returns boolean
*/
handle(event, context) {
return __awaiter(this, void 0, void 0, function* () {
if (!event)
throw new Error('@event is required!');
//* WARN! allows for using callbacks as finish/error-handlers
if (context)
context.callbackWaitsForEmptyEventLoop = false;
//* Check API parameters.
const main = (event, context, callback) => {
const type = this.findService(event);
(0, engine_1._log)(NS, `main(${type})...`);
const handler = this._map[type];
if (handler && typeof handler == 'function') {
//* low level handler function.
return handler(event, context, callback);
}
else if (handler && typeof handler == 'object') {
//* must be `LambdaHandlerService`.
const $svc = handler;
const $ctx = $svc.packContext
? $svc.packContext(event, context)
: this.packContext(event, context);
if ($ctx && $ctx instanceof Promise) {
return $ctx.then(_ => $svc.handle(event, _));
}
else if ($ctx) {
return $svc.handle(event, $ctx);
}
return $svc.handle(event, null);
}
//* raise error if not found.
(0, engine_1._inf)(NS, `WARN! unknown[${type}].event =`, engine_1.$U.json(event));
callback && callback(new Error(`400 UNKNOWN - service:${type}`));
};
//* call promised.
const promise = (main, event, context) => new Promise((resolve, reject) => {
try {
let resolved = false;
const R = main(event, context, (error, result) => {
error && (0, engine_1._err)(NS, '! err@cb =', error);
// !error && _inf(NS, '! res@cb =', result);
if (error)
reject(error);
else if (!resolved)
resolve(result);
});
if (R !== undefined) {
resolved = true;
resolve(R);
}
}
catch (e) {
return reject(e);
}
});
//* call main.. (it will return result or promised)
return promise(main, event, context)
.then(_ => {
// if (_ !== undefined) _log(NS, '! res =', $U.json(_));
if (_ !== undefined)
(0, engine_1._log)(NS, '! res =', engine_1.$U.S(_, 320, 64, ' .... ')); //* cut result string.
// ((context && context.done) || callback)(null, _);
// return true;
return _;
})
.catch(e => {
(0, engine_1._err)(NS, '! err =', e);
if (!LambdaHandler.REPORT_ERROR) {
// ((context && context.done) || callback)(e, null);
// return false;
throw e;
}
//* report this error.
return (0, engine_1.doReportError)(e, context, event)
.catch(_ => _) // safe call w/o error.
.then(() => {
// ((context && context.done) || callback)(e, null);
// return false;
throw e;
});
});
});
}
/**
* handle param via protocol-service.
* - sub-service could call this method() to bypass request.
*
* @param param protocol parameters
*/
handleProtocol(param) {
return __awaiter(this, void 0, void 0, function* () {
//* if valid API Request, then use $web's function.
const $web = this._map['web'];
if (!$web || typeof $web != 'object')
throw new Error(`500 NO WEB HANDLER - name:web`);
return $web.handleProtocol(param);
});
}
/**
* (default) pack the origin context to application context.
* - override this function if required!
*
* @param event origin event
* @param $ctx origin context of lambda
*/
packContext(event, $ctx) {
return __awaiter(this, void 0, void 0, function* () {
const context = {};
$ctx && (0, engine_1._log)(NS, `! context[${$ctx.functionName || ''}] =`, engine_1.$U.json($ctx));
return context;
});
}
}
exports.LambdaHandler = LambdaHandler;
//* shared config.
LambdaHandler.REPORT_ERROR = engine_1.$U.env('REPORT_ERROR', '1') == '1';
//# sourceMappingURL=lambda-handler.js.map
;