lemon-core
Version:
Lemon Serverless Micro-Service Platform
639 lines • 23.9 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());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.my_sequence = exports.my_parrallel = exports.parseRange = exports.isUserAuthorized = exports.getIdentityId = exports.$event = exports.$slack = exports.$info = exports.$protocol = exports.$rand = exports.$T = void 0;
/**
* `helpers.ts`
* - helper functions used globally in project
*
*
* @author Steve Jung <steve@lemoncloud.io>
* @date 2020-12-22 initial version
* @date 2021-12-21 protocol support `//self` url.
* @date 2021-12-23 optimize types of $protocol.
* @date 2022-03-17 addition text processing. (S2, P)
*
* @copyright (C) 2021 LemonCloud Co Ltd. - All Rights Reserved.
*/
const cores_1 = __importDefault(require("../cores/"));
const engine_1 = require("../engine/");
const test_helper_1 = require("../common/test-helper");
const querystring_1 = __importDefault(require("querystring"));
const perf_hooks_1 = require("perf_hooks");
/**
* Helpers to transform data-types.
*/
exports.$T = {
/**
* transform to string w/ trim()
*/
S: (val, def = '') => `${val !== null && val !== void 0 ? val : def}`.trim(),
/**
* as string w/o white-space.
*/
S2: (val, def = '', delim = '') => `${val !== null && val !== void 0 ? val : def}`.replace(/\s+/g, delim),
/**
* transform to string[]
*/
SS: (val, def = []) => {
if (val === null || val === undefined)
return def;
if (typeof val === 'string')
return val ? val.split(',').map(_ => exports.$T.S(_, '').trim()) : def;
if (Array.isArray(val))
return val.length > 0 ? val.map(_ => exports.$T.S(_, '').trim()) : def;
return [exports.$T.S(val)];
},
/**
* text to Plain text (remove html tag)
*/
P: (text, max = 0) => {
const msg = (typeof text === 'string' ? text : `${text || ''}`)
.replace(/<[^>]*>/g, ' ') //! remove html tag.
.replace(/[^a-zA-Z0-9가-힣ㅋ-ㅎㅏ-ㅣ\.\?]+/g, ' ') //! remove non-char.
.trim();
const len = msg.length;
return max && len > max ? msg.substring(0, max) + '...' : msg;
},
/**
* transform to number(integer).
*/
N: (val, def = 0) => {
const n = engine_1.$U.N(val, def);
return Number.isNaN(n) ? def : n;
},
/**
* number array
*/
NN: (val, def = []) => {
if (val === null || val === undefined)
return def;
if (typeof val === 'string')
return val ? val.split(',').map(_ => exports.$T.N(_, 0)) : def;
if (Array.isArray(val))
return val.length > 0 ? val.map(_ => exports.$T.N(_, 0)) : def;
return [exports.$T.N(val)];
},
/**
* transform to number(float)
*/
F: (val, def = 0) => engine_1.$U.F(val, def),
/**
* transform to number(float)[]
*/
FF: (val, def = []) => {
if (val === null || val === undefined)
return def;
if (typeof val === 'string')
return val ? val.split(',').map(_ => exports.$T.F(_, 0)) : def;
if (Array.isArray(val))
return val.length > 0 ? val.map(_ => exports.$T.F(_, 0)) : def;
return [exports.$T.F(val)];
},
/**
* float w/ fixed len=3
*/
F3: (n, e = 0.000001) => Number((n + e).toFixed(3)),
/**
* transform to boolean.
*/
B: (val, def = 0) => {
if (val === null || val === undefined)
return def;
if (typeof val === 'boolean')
return val ? 1 : 0;
if (typeof val === 'string' && ['y', 'yes', 't', 'true'].includes(val.toLowerCase()))
return 1;
return engine_1.$U.N(val, def) && 1;
},
/**
* transform to Time number via string | number.
*/
T: (val, def = 0) => {
const checkVal = `${val || ''}`.includes('-');
if (checkVal) {
if (engine_1.$U.dt(val))
return engine_1.$U.dt(val).getTime();
else
throw new Error(`@val[${val}] is invalid!`);
}
else {
return engine_1.$U.dt(engine_1.$U.N(val, def)).getTime();
}
},
/**
* transform to Date formatted string
*/
D: (val, def = '') => {
let s = exports.$T.S(val);
let y;
let m;
let d;
if (s.includes('-')) {
[y, m, d] = s.split('-');
}
else {
y = s.slice(0, 4);
m = s.slice(4, 6);
d = s.slice(6);
}
s = [y, m, d]
.filter(e => (e === null || e === void 0 ? void 0 : e.length) > 0)
.map(e => e.padStart(2, '0'))
.join('-');
if (y && y.length === 4 && !Number.isNaN(Date.parse(s)))
return s;
return def;
},
/**
* date-time format
*/
DT: (val, def = '2020-01-01') => {
const s = exports.$T.D(val, '').split('-'); // must be valid date-format like '2000-01-02'
const d = def.split('-');
return d
.map((d, i) => s[i] || d || '01')
.map(e => e.padStart(2, '0'))
.join('-');
},
/**
* Extract Text
*/
EX: (data, txt1, txt2) => {
data = `${data || ''}`;
const a = data.indexOf(txt1);
const b = a >= 0 ? data.indexOf(txt2, a + txt1.length) : a;
return b > a ? data.substring(a + txt1.length, b) : '';
},
/**
* transform to simple-set.
* @param val json object.
*/
simples: (val, throws = false) => {
//! validate if simple-type (string | number | null | undefined)
const t = typeof val;
if (val === undefined)
return undefined;
else if (val === null || val === '')
return { _: null };
else if (t === 'string' || t === 'number')
return { _: val };
else if (t === 'object' && !Array.isArray(val)) {
const keys = Object.keys(val);
const reName = /^[a-z_][a-zA-Z0-9_\-]*$/;
return keys.reduce((N, k) => {
const v = val[k];
if (v === undefined) {
//! NOP
}
else if (reName.test(k)) {
const t = typeof v;
if (v === null || v === '')
N[k] = null;
else if (t === 'string' || t === 'number')
N[k] = v;
else if (throws)
throw new Error(`.${k}[${v}] is invalid!`);
}
else if (throws)
throw new Error(`.${k} is invalid format!`);
return N;
}, {});
}
else if (throws)
throw new Error(`@val[${t}] is invalid!`);
return {};
},
/**
* catch string between txt1 and txt2
* @param data string
* @param txt1 head
* @param txt2 tail
*/
catch: (data, txt1, txt2) => {
data = typeof data == 'string' ? data : `${data}`;
const a = data.indexOf(txt1);
const b = a >= 0 ? data.indexOf(txt2, a + txt1.length) : a;
const c = b > a ? data.substring(a + txt1.length, b) : '';
return c;
},
/**
* merge simple-set from $org to $new
* @param $org the origin set
* @param $new the update set.
*/
merge: ($org, $new) => {
if (!$new)
return $org;
return Object.keys($new).reduce((N, k) => {
const val = $new[k];
if (val === null || val === undefined)
delete N[k];
else
N[k] = val;
return N;
}, Object.assign({}, $org));
},
/**
* replace message with template.
*/
template: (msg, set) => {
// const msg = $U.env('MSG_PHONE_CODE', '인증 번호는 [{code}] 입니다.') as string;
const tmp = Object.assign({}, set);
return msg.replace(/\{(\w+)\}/g, (a, b) => (tmp[b] !== undefined ? `${tmp[b]}` : `{${b}}`));
},
/**
* make random-code by length
* @param size length of code
* @param rand flag to use random (0 => 0, 1 => max)
*/
makeRandomCode: (size = 6, rand) => {
const flag = rand === undefined || rand === true || typeof rand == 'number' ? true : false;
const min = size >= 1 ? Math.pow(10, size - 1) : 1;
const max = 10 * min - 1;
const val = min + (flag ? Math.floor((max - min) * (typeof rand == 'number' ? rand : Math.random())) : max - min);
return { val, min, max };
},
/**
* 객체 정규화 시킴.
* - null 에 대해서는 특별히 처리.
*/
normal: (N) => Object.keys(N || {}).reduce((M, k) => {
if (k.startsWith('_') || k.startsWith('$'))
return M;
const v = N[k];
//! `null` 은 DynamoDB에서 비어있는 문자임.
M[k] = v === null ? '' : v;
return M;
}, {}),
/**
* transform list to map by `id`
*/
asMap: (list, id = 'id') => list.reduce((M, N) => {
const key = `${N[id] || ''}`;
M[key] = N;
return M;
}, {}),
/**
* compare object, and extract the only diff properties.
*/
diff: (A, B, onlyValid = false) => {
if (!A || !B)
return B;
else if (typeof A !== 'object' || typeof B !== 'object')
return B;
return engine_1.$U
.diff(A, B)
.map(s => `${s || ''}`)
.reduce((M, k) => {
const org = A[k];
const val = B[k];
if (onlyValid) {
if (val !== undefined && val !== null) {
//! dynamo 에서는 null 과 '' 이 같음.
if (org === null && val === '') {
// NOP - due to same value.
}
else {
M[k] = val;
}
}
}
else {
M[k] = val === undefined && org !== undefined ? null : val;
}
return M;
}, {});
},
/**
* get $perf instance.
* ```ts
* const p = $T.perf()
* const took = p.took();
*/
perf: () => {
return new (class MyPerfmance {
constructor(t0) {
this.took = () => {
const t1 = perf_hooks_1.performance.now(); // start of processing
const took = Math.round((t1 - this.t0) / 100) / 10; // in sec.
return took;
};
this.t0 = t0 || perf_hooks_1.performance.now(); // start of processing
}
})();
},
/**
* parse `.meta` property as object.
* @param meta any
*/
parseMeta: (meta) => {
if (typeof meta === 'string' && meta) {
try {
if (meta.startsWith('[') && meta.endsWith(']')) {
const list = JSON.parse(meta);
const $ret = { list };
return $ret;
}
else if (meta.startsWith('{') && meta.endsWith('}')) {
return JSON.parse(meta);
}
else {
const $ret = { type: 'string', value: meta };
return $ret;
}
}
catch (e) {
const $ret = { type: 'string', value: meta, error: (0, test_helper_1.GETERR)(e) };
return $ret;
}
}
else if (meta === null || meta === undefined) {
return null;
}
else if (typeof meta === 'object') {
return meta;
}
else {
const type = typeof meta;
const $ret = { type, value: meta };
return $ret;
}
},
/**
* clear the undefined properties from the cloned object.
* - applied only to 1st depth.
*
* @param N object
* @param $def default if not valid object.
* @returns cloned object
*/
onlyDefined: (N, $def = null) => N && typeof N === 'object'
? Object.entries(N).reduce((N, [k, v]) => {
if (v !== undefined)
N[k] = v;
return N;
}, {})
: $def,
};
/**
* random number generator
*/
exports.$rand = {
/**
* list of number[] in n-size.
*/
range: (n) => [...Array(n).keys()],
/**
* generate random number
*/
float: (from, to) => Math.random() * (to - from) + from,
/**
* generate multiple float numbers
*/
floats: (from, to, n) => new Array(n).fill(0).map(() => exports.$rand.float(from, to)),
/**
* generate an integer
*/
integer: (from, to) => Math.floor(exports.$rand.float(Math.ceil(from), Math.floor(to))),
/**
* generate multiple integers
*/
integers: (from, to, n) => new Array(n).fill(0).map(() => exports.$rand.integer(from, to)),
};
/**
* builder to support protocol-service.
* @param context the current context (or service name).
* @param service service name
* @param options additional options.
*/
const $protocol = (context = {}, service, options) => {
//! for backward compartibility. shift arguments if 1st context is string.
const ctx = typeof context === 'string' ? {} : context;
service = typeof context === 'string' ? context : service;
const param = typeof context === 'string' ? service : options === null || options === void 0 ? void 0 : options.param;
const body = typeof context === 'string' ? param : options === null || options === void 0 ? void 0 : options.body;
if (!ctx)
throw new Error(`@context (NextContext) is required!`);
if (!service)
throw new Error(`@service (string) is required!`);
const $proto = cores_1.default.protocol.service;
const isProd = (options === null || options === void 0 ? void 0 : options.isProd) !== undefined ? options === null || options === void 0 ? void 0 : options.isProd : engine_1.$U.env('NS') === 'SS' ? true : false;
//TODO - `STAGE` is not changed from env.yml file @211215.
// _inf(NS, 'NS =', $U.env('NS'), $engine.cores.config.config.get('NS'), process.env['NS']);
// _inf(NS, 'stage =', $U.env('STAGE'), $engine.cores.config.config.get('STAGE'), process.env['STAGE']); //NOTE - STAGE is not changed.
//! prod용 lambda접근을 위한 환경 구성!!!!!
const $param = (p, b, x) => {
const protoParam = Object.assign(Object.assign({}, $proto.fromURL(ctx, asTargetUrl(), p || param, b || body)), x);
if (isProd)
protoParam.stage = 'prod';
return protoParam;
};
const $callback = (callback) => {
if (callback) {
const [path, qs] = callback.split('?');
if (path) {
const [type, id, cmd] = path.split('/');
const param = querystring_1.default.parse(qs);
return { type, id, cmd, param };
}
}
};
//! find the target protocol-url from context.
const asTargetUrl = () => {
if (!service.startsWith('//'))
throw new Error(`@service[${service}] (string) is invalid!`);
if (service.startsWith('//self/')) {
const self = $proto.myProtocolURI(ctx);
const [a, b] = [self.indexOf('@'), self.indexOf('#')];
const target = self.substring(a < 0 ? 'api://'.length : a + 1, b > a ? b : self.length) +
service.substring('//self'.length);
return `api://${target}`;
}
else {
return `api:${service}`;
}
};
//! execute via protocol-service.
const execute = (param, body, mode = 'POST') => $proto.execute($param(param, body, { mode }));
// eslint-disable-next-line prettier/prettier
const enqueue = (param, body, mode = 'POST', callback, delaySeconds = 1) => $proto.enqueue($param(param, body, { mode }), $callback(callback), delaySeconds);
const notify = (param, body, mode = 'POST', callback) => $proto.notify($param(param, body, { mode }), $callback(callback));
//! returns instance.
return {
hello: () => `helper:protocol:${service || ''}`,
asTargetUrl,
execute,
enqueue,
notify,
};
};
exports.$protocol = $protocol;
/**
* get the current config info
*/
const $info = () => {
const $conf = cores_1.default.config.config;
const service = $conf.getService();
const version = $conf.getVersion();
const stage = $conf.getStage();
return { service, version, stage };
};
exports.$info = $info;
/**
* send message to slack/public
*
* @param title 헤터 타이틀
* @param text object or 텍스트 내용
* @param pretext (optional) 텍스트 미리보기용.
* @param params (optional) customize more options.
*/
const $slack = (title, text, pretext, params) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b;
//! about current service.................
const { service, version, stage } = (0, exports.$info)();
const name = `${service}#${version}` + (stage !== 'prod' ? `/${stage}` : '');
return (0, engine_1.doReportSlack)((params === null || params === void 0 ? void 0 : params.channel) ? `!${params === null || params === void 0 ? void 0 : params.channel}` : 'public', {
channel: (_a = params === null || params === void 0 ? void 0 : params.channel) !== null && _a !== void 0 ? _a : undefined,
attachments: [
exports.$T.onlyDefined({
color: `${(params === null || params === void 0 ? void 0 : params.color) || '#FFB71B' || 'good'}`,
title,
pretext: pretext === null
? undefined
: pretext !== null && pretext !== void 0 ? pretext : ((params === null || params === void 0 ? void 0 : params.scope) ? `#${name} [\`${params.scope}\`]` : undefined),
text: text === null || text === undefined
? undefined
: typeof text === 'string'
? text
: engine_1.$U.json(text),
fields: params === null || params === void 0 ? void 0 : params.fields,
footer: (params === null || params === void 0 ? void 0 : params.footer) === null ? undefined : (_b = params === null || params === void 0 ? void 0 : params.footer) !== null && _b !== void 0 ? _b : `${service}/${stage}#${version}`,
ts: (params === null || params === void 0 ? void 0 : params.ts) === null ? undefined : Math.floor(engine_1.$U.current_time_ms() / 1000),
}),
],
}, params === null || params === void 0 ? void 0 : params.context).catch(e => `#err:${(0, test_helper_1.GETERR)(e)}`);
});
exports.$slack = $slack;
/**
* event producer builder
* @param context current context
* @param defEndpoint (optional) the default endpoint.
*/
const $event = (context, defEndpoint = '') => {
const $protocol = cores_1.default.protocol;
const endpoint = engine_1.$U.env('EVENT_RELAY_SNS', defEndpoint);
if (!endpoint)
throw new Error(`env[EVENT_RELAY_SNS] is required - $event()`);
return {
publish: (body) => __awaiter(void 0, void 0, void 0, function* () { return $protocol.service.broadcast(context, endpoint, body); }),
};
};
exports.$event = $event;
/**
* authentication helper - get identity-id from context
* @param context the current context
*/
function getIdentityId(context) {
var _a;
const identityId = (_a = context === null || context === void 0 ? void 0 : context.identity) === null || _a === void 0 ? void 0 : _a.identityId;
if (!identityId && (context === null || context === void 0 ? void 0 : context.domain) === 'localhost') {
//! use `env[LOCAL_ACCOUNT]` only if runs in local server.
return engine_1.$U.env('LOCAL_ACCOUNT', '');
}
return identityId;
}
exports.getIdentityId = getIdentityId;
/**
* authentication helper - check user is authorized
* - 이 메서드는 AWS IAM 인증 여부만을 확인한다.
* - 따라서 true를 반환한다고 하여 회원 가입이 되어있다는 의미는 아니다.
*
* @param context the current context
* @params params (optional) to override `identity` when running local.
*/
function isUserAuthorized(context, params) {
const identityId = getIdentityId(context);
//WARN - in local server, override the identity w/ param
if ((context === null || context === void 0 ? void 0 : context.domain) === 'localhost') {
//!* override with optional parameter.
if (context) {
context.identity = Object.assign(Object.assign({}, (params !== undefined ? params : context.identity)), { identityId });
}
}
return !!identityId;
}
exports.isUserAuthorized = isUserAuthorized;
/**
* parse range expression
* @param exp range expression (e.g. '[63100 TO 224000]' or '[* TO 150000}')
*/
function parseRange(exp) {
const match = exp.match(/^([\[{])([0-9]+|\*) TO ([0-9]+|\*)([}\]])$/);
if (match && (match[2] !== '*' || match[3] !== '*')) {
const range = {};
if (match[2] !== '*') {
const n = exports.$T.N(match[2]);
if (match[1] === '[')
range.gte = n;
else if (match[1] === '{')
range.gt = n;
}
if (match[3] !== '*') {
const n = exports.$T.N(match[3]);
if (match[4] === ']')
range.lte = n;
else if (match[4] === '}')
range.lt = n;
}
return range;
}
}
exports.parseRange = parseRange;
/**
* customized of `do_parrallel` for safe error-handling.
* - use `.error` to report the internal error.
*
* @param list list of model.
* @param func callback to process of each
* @param size (optional) size of parrallel (default 10)
*/
const my_parrallel = (list, func, size) => __awaiter(void 0, void 0, void 0, function* () {
const results = yield (0, engine_1.do_parrallel)(list, (item, i) => {
const ret = (() => {
try {
return func(item, i);
}
catch (e) {
return Promise.reject(e);
}
})();
const res = ret instanceof Promise ? ret : Promise.resolve(ret);
return res.catch(e => ({ id: item.id, error: (0, test_helper_1.GETERR)(e) }));
}, size);
return results;
});
exports.my_parrallel = my_parrallel;
/**
* run in sequence order
* - same as `my_parrallel(list, func, 1)`;
*
* 주의) 내부 error를 throw 하지 않으니, list 를 전부 처리할때까지 안끝남.
*
* @param list list of model.
* @param func callback to process of each
*/
const my_sequence = (list, func) => (0, exports.my_parrallel)(list, func, 1);
exports.my_sequence = my_sequence;
//# sourceMappingURL=helpers.js.map
;