lemon-core
Version:
Lemon Serverless Micro-Service Platform
382 lines • 15.6 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.convDateToTS = exports.convDateToTime = exports.convDate = exports.DEFAULT_TIME_ZONE = exports.do_parrallel = exports.doReportMetric = exports.doReportSlack = exports.doReportCallback = exports.doReportError = exports.getHelloArn = void 0;
/**
* `core/engine.ts`
* - shared core engine's export.
*
* **NOTE**
* - override `process.env` before use(or import) this.
*
* ```js
* //! import core engine like this.
* import { $engine, _log, _inf, _err, $U } from '../core/engine';
* const NS = $U.NS(name, 'yellow');
* _inf(NS, `! model[${name}] is ready..`);
* ```
*
* @author Steve Jung <steve@lemoncloud.io>
* @date 2019-05-24 initial version in `lemon-todaq-api`.
* @date 2019-08-01 support `loadJsonSync()` + move common functions + export core services + '$web'
* @date 2019-08-02 improved type helper with `lemon-engine#2.2.0` + fix $client() error.
* @date 2019-08-06 improved type helper with `lemon-engine#2.2.3`
* @date 2019-08-08 improved `$api().do(event, context, callback)`.
* @date 2019-11-26 cleanup and optimized for `lemon-core#v2`
* @date 2022-02-21 remove `$_` the lodash libs.
*
* @copyright (C) lemoncloud.io 2019 - All Rights Reserved.
*/
const index_1 = require("./index");
const shared_1 = require("../tools/shared");
const aws_sns_service_1 = require("../cores/aws/aws-sns-service");
//! create SNS Service
const $sns = (arn) => new aws_sns_service_1.AWSSNSService(arn);
/**
* find ARN('lemon-hello-sns') via context information or environment.
*
* @param context the current running context
* @param NS namespace to log
*/
const getHelloArn = (context, NS) => {
NS = NS || index_1.$U.NS('HELO');
//! use pre-defined env via `serverless.yml`
const arn = index_1.$engine.environ('REPORT_ERROR_ARN', '');
if (arn.startsWith('arn:aws:sns:'))
return arn;
if (!context)
throw new Error(`@context (RequestContext) is required!`);
if (true) {
const target = 'lemon-hello-sns';
const $ctx = context;
const $req = context;
const $ncx = context;
//! build arn via context information.
const invokedFunctionArn = `${$ctx.invokedFunctionArn || ''}`; // if called via lambda call ex: 'arn:aws:lambda:ap-northeast-2:085403634746:function:lemon-messages-api-prod-user'
const accountId = `${$ncx.accountId || invokedFunctionArn.split(':')[4] || $req.accountId || ''}`;
const region = invokedFunctionArn.split(':')[3] || `ap-northeast-2`;
(0, index_1._inf)(NS, '! accountId =', accountId);
if (!accountId) {
(0, index_1._err)(NS, 'ERROR! missing accountId. context =', index_1.$U.json(context));
throw new Error('.accountId is missing');
}
return `arn:aws:sns:${region}:${accountId}:${target}`;
}
};
exports.getHelloArn = getHelloArn;
/**
* report error via `lemon-hello-sns`.
*
* @param e Error
* @param context Lambda Context
* @param event Origin Event Object.
* @param data Optinal Data(body).
*/
const doReportError = (e, context, event, data) => __awaiter(void 0, void 0, void 0, function* () {
//! ignore only if local express-run.
if (context && context.source === 'express')
return '!ignore';
const NS = index_1.$U.NS('RPTE');
//TODO - optimize message extractor.
const $message = (e) => {
const m = (e && (e.message || e.statusMessage)) || e;
return typeof m == 'object' ? index_1.$U.json(m) : `${m}`;
};
(0, index_1._log)(NS, `doReportError(${$message(e)})...`);
//! dispatch invoke conditins.
try {
const message = $message(e);
const $pack = (shared_1.loadJsonSync && (0, shared_1.loadJsonSync)('package.json')) || {};
const name = (context && context.name) || process.env.NAME || '';
const stage = (context && context.stage) || process.env.STAGE || '';
const apiId = (context && context.apiId) || '';
const domainPrefix = (context && context.domainPrefix) || '';
const resourcePath = (context && context.resourcePath) || '';
const identity = (context && context.identity) || {};
const service = `api://${$pack.name || 'lemon-core'}/${name}-${stage}#${$pack.version || '0.0.0'}`;
//! prepare payload to publish.
const payload = {
service,
message,
context: Object.assign(Object.assign({}, context), { stage, apiId, resourcePath, identity, domainPrefix, event }),
data,
};
//! find target arn.
const arn = (0, exports.getHelloArn)(context, NS);
(0, index_1._log)(NS, `> report-error.arn =`, arn);
return $sns(arn)
.reportError(e, payload, arn)
.then((mid) => {
(0, index_1._inf)(NS, '> err.message-id =', mid);
return `${mid}`;
})
.catch((e) => {
(0, index_1._err)(NS, '! err.report =', e);
return '';
});
}
catch (e2) {
(0, index_1._err)(NS, '! err-ignored =', e2);
return `!err - ${e2.message || e2}`;
}
});
exports.doReportError = doReportError;
/**
* send callback data via web-hook endpoint.
*
* TODO - improve function identity.!! @191212.
*
* @param data payload
*/
const doReportCallback = (data, service, context) => __awaiter(void 0, void 0, void 0, function* () {
const NS = index_1.$U.NS('callback', 'cyan');
try {
const $pack = (shared_1.loadJsonSync && (0, shared_1.loadJsonSync)('package.json')) || {};
const stage = `${index_1.$U.env('STAGE', 'local')}`.toLowerCase();
const name = `${index_1.$U.env('NAME', '')}`.toLowerCase();
service = service || `api://${$pack.name || 'lemon-core'}#${$pack.version || '0.0.0'}/${name}-${stage}`;
const payload = { service, data };
const arn = (0, exports.getHelloArn)(context, NS);
return $sns(arn)
.publish('', 'callback', payload) // subject should be 'callback'
.then((mid) => {
(0, index_1._inf)(NS, '> callback.res =', mid);
return `${mid}`;
})
.catch((e) => {
(0, index_1._err)(NS, '! callback.err =', e);
return '';
});
}
catch (e) {
(0, index_1._err)(NS, '> reportCallback.err =', e);
return (0, exports.doReportError)(e, context, null, data);
}
});
exports.doReportCallback = doReportCallback;
/**
* report slack message via `lemon-hello-sns`.
*
* @param channel channel of slack
* @param body slack body
* @param context current running context.
*/
const doReportSlack = (channel, body, context) => __awaiter(void 0, void 0, void 0, function* () {
const NS = index_1.$U.NS('RPTS');
(0, index_1._log)(NS, `doReportSlack()...`);
//! dispatch invoke conditins.
try {
const $pack = (shared_1.loadJsonSync && (0, shared_1.loadJsonSync)('package.json')) || {};
const service = `api://${$pack.name || 'lemon-core'}#${$pack.version || '0.0.0'}`;
const stage = (context && context.stage) || '';
const apiId = (context && context.apiId) || '';
const domainPrefix = (context && context.domainPrefix) || '';
const resourcePath = (context && context.resourcePath) || '';
const identity = (context && context.identity) || {};
const param = {};
//! prepare payload to publish.
const payload = {
channel,
service,
param,
body,
context: { stage, apiId, resourcePath, identity, domainPrefix },
};
//! find target arn.
const arn = (0, exports.getHelloArn)(context, NS);
(0, index_1._log)(NS, `> report-slack.arn =`, arn);
return $sns(arn)
.publish(arn, 'slack', payload)
.then((mid) => {
(0, index_1._inf)(NS, '> sns.message-id =', mid);
return `${mid}`;
})
.catch((e) => {
(0, index_1._err)(NS, '! err.slack =', e);
return '';
});
}
catch (e2) {
(0, index_1._err)(NS, '! err-ignored =', e2);
return `!err - ${e2.message || e2}`;
}
});
exports.doReportSlack = doReportSlack;
/**
* report metric-data like chart/graph to record via `lemon-metrics-sns`.
*
* @param ns namespace like `[a-zA-Z][a-zA-Z0-9]+`
* @param id id value like `[a-zA-Z0-9][a-zA-Z0-9_:\-]+`
* @param body any body data
* @param context current running context.
*/
const doReportMetric = (ns, id, body, context) => __awaiter(void 0, void 0, void 0, function* () {
const NS = index_1.$U.NS('RPTM');
//! validate parameters. (see `lemon-metrics-api`)
const reNs = /^[a-zA-Z][a-zA-Z0-9]+$/;
const reId = /^[a-zA-Z0-9][a-zA-Z0-9_:\-]+$/;
if (!reNs.test(ns))
throw new Error('Invalid text-format @ns:' + ns);
if (!reId.test(id))
throw new Error('Invalid text-format @id:' + id);
(0, index_1._log)(NS, `doReportMetric(${ns},${id})...`);
//! dispatch invoke conditins.
try {
const $pack = (shared_1.loadJsonSync && (0, shared_1.loadJsonSync)('package.json')) || {};
const service = `api://${$pack.name || 'lemon-core'}#${$pack.version || '0.0.0'}`;
const stage = (context && context.stage) || '';
const apiId = (context && context.apiId) || '';
const domainPrefix = (context && context.domainPrefix) || '';
const resourcePath = (context && context.resourcePath) || '';
const identity = (context && context.identity) || {};
const param = { ns, id };
//! prepare payload: `POST /metrics/!/report`
const payload = {
service,
type: 'metrics',
method: 'post',
id: '!',
cmd: 'report',
param,
body,
context: { stage, apiId, resourcePath, identity, domainPrefix },
};
//! find metric-arn via error-arn.
const target = 'lemon-metrics-sns';
const arn0 = (0, exports.getHelloArn)(context, NS);
// eslint-disable-next-line prettier/prettier
const arn = arn0.startsWith('arn:aws:sns:') && arn0.split(':').length == 6 ? arn0.split(':').map((v, i) => i == 5 ? target : v).join(':') : arn0;
(0, index_1._log)(NS, `> report-metric.arn =`, arn);
return $sns(arn)
.publish(arn || target, 'metric', payload)
.then((mid) => {
(0, index_1._inf)(NS, '> sns.message-id =', mid);
return `${mid}`;
})
.catch((e) => {
(0, index_1._err)(NS, '! err.metric =', e);
return '';
});
}
catch (e2) {
(0, index_1._err)(NS, '! err-ignored =', e2);
return `!err - ${e2.message || e2}`;
}
});
exports.doReportMetric = doReportMetric;
/**
* parrallel actions in list (in batch-size = 10)
*
* **TODO** - improve return types by refering callback.
*
* @param param any list
* @param callback (item)=>any | Promise<any>
* @param size (optional) size of parrallel (default 10)
* @param pos (optional) current pos (default 0)
* @param result (optional) result set in stacked.
*/
const do_parrallel = (param, callback, size = 10, pos = 0, result = []) => {
size = size === undefined ? 10 : size;
pos = pos === undefined ? 0 : pos;
result = result === undefined ? [] : result;
//! annonymous method of callback
const safeCall = (n, i) => {
try {
return callback(n, i);
}
catch (e) {
return Promise.reject(e);
}
};
// _log(NS, `! parrallel(${pos}/${size})`)
const list = Array.isArray(param) ? param : param.list;
const list2 = list.slice(pos, pos + size);
const actions = list2.map((node, i) => {
const index = pos + i;
try {
//! error proof.
const R = safeCall(node, index);
if (R && typeof R == 'object' && R instanceof Promise) {
const R2 = R; // avoid compile error.
return R2.catch(e => {
(0, index_1._err)(`!ERR@1 node[${index}] =`, e);
//! make sure error instance.
return e instanceof Error ? e : new Error(typeof e == 'string' ? e : JSON.stringify(e));
});
}
return R;
}
catch (e) {
(0, index_1._err)(`!ERR@2 node[${index}] =`, e);
//! make sure error instance.
return e instanceof Error ? e : new Error(typeof e == 'string' ? e : JSON.stringify(e));
}
});
//! do parrallel.
return Promise.all(actions)
.then(res => {
if (Array.isArray(param))
return res;
const { ignoreError, reportError, event, context, message } = param;
const errors = res.filter(i => i instanceof Error);
if (!errors.length)
return res;
const data = { message, pos, size };
data.errors = res.map((_, i) => {
if (!(_ instanceof Error))
return '';
return { error: _.message, node: list2[i] };
});
return (reportError ? (0, exports.doReportError)(errors[0], context, event, data) : Promise.resolve('')).then(() => {
if (ignoreError) {
res = res.map((_, i) => (_ instanceof Error ? list2[i] : _));
}
return res;
});
})
.then(_ => {
result = result.concat(_);
if (!_.length)
return Promise.resolve(result);
return (0, exports.do_parrallel)(param, callback, size, pos + size, result);
});
};
exports.do_parrallel = do_parrallel;
//! default time-zone for this api. (Asia/Seoul - 9 hours)
exports.DEFAULT_TIME_ZONE = 9;
//! convert to date of input.
const convDate = (dt) => index_1.$U.dt(dt, exports.DEFAULT_TIME_ZONE);
exports.convDate = convDate;
/**
* Convert input to time value (in number)
*
* @param {*} dt see `conv_date()`
* @param {*} name name of property
*/
const convDateToTime = (dt) => {
if (dt === '' || dt === '0' || dt === 0)
return 0; // 0 means null (not-set)
const t = (0, exports.convDate)(dt);
return t.getTime();
};
exports.convDateToTime = convDateToTime;
/**
* Convert input (Date) to time-stamp (YYYY-MM-DD hh:mm:ss)
* - consider with current time-zone.
*
* @param {*} dt
*/
const convDateToTS = (dt) => {
const t = (0, exports.convDate)(dt);
return index_1.$U.ts(t, exports.DEFAULT_TIME_ZONE);
};
exports.convDateToTS = convDateToTS;
//# sourceMappingURL=engine.js.map
;