lemon-core
Version:
Lemon Serverless Micro-Service Platform
179 lines • 9.36 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.LambdaDynamoStreamHandler = void 0;
/**
* `lambda-dynamo-stream-handler.ts`
* - lambda handler to process Dynamo DB Stream event.
*
*
* @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 lambda_handler_1 = require("./lambda-handler");
const dynamodb_value_1 = require("../../lib/dynamodb-value");
const NS = engine_1.$U.NS('HDBS', 'green'); // NAMESPACE TO BE PRINTED.
/**
* class: LambdaDynamoStreamHandler
* - default DynamoDBStream Handler w/ event-listeners.
*/
class LambdaDynamoStreamHandler extends lambda_handler_1.LambdaSubHandler {
/**
* default constructor w/ registering self.
*/
constructor(lambda, register) {
super(lambda, register ? 'dds' : undefined);
this.listeners = [];
//* for debugging. save last result
this.$lastResult = null;
/**
* Default Handler.
*/
this.handle = (event, context) => __awaiter(this, void 0, void 0, function* () {
//* for each records.
const records = event.Records || [];
(0, engine_1._log)(NS, `handle(len=${records.length})...`);
// _log(NS, '> event =', $U.json(event));
const $doReportError = (0, lambda_handler_1.buildReportError)(LambdaDynamoStreamHandler.REPORT_ERROR);
const onStreamRecord = (record, i) => __awaiter(this, void 0, void 0, function* () {
const region = record.awsRegion;
const eventId = record.eventID;
const eventName = record.eventName;
const tableName = (record.eventSourceARN && record.eventSourceARN.split('/')[1]) || '';
(0, engine_1._log)(NS, `> record[${i}].eventName/tableName =`, eventName, tableName);
const dynamodb = record.dynamodb;
if (!dynamodb)
return; // ignore this.
const $key = dynamodb.Keys ? (0, dynamodb_value_1.toJavascript)(dynamodb.Keys, null) : null;
const $new = dynamodb.NewImage ? (0, dynamodb_value_1.toJavascript)(dynamodb.NewImage, null) : null; // null if eventName == 'REMOVE'
const $old = dynamodb.OldImage ? (0, dynamodb_value_1.toJavascript)(dynamodb.OldImage, null) : null; // null if eventName == 'INSERT'
//* 이제 변경된 데이터를 추적해서, 이후 처리 지원. (update 는 호출만되어도 이벤트가 발생하게 됨)
const diff = eventName === 'MODIFY' ? engine_1.$U.diff($old, $new) : [];
const node = $new || $old || {}; // make sure not null.
const prev = diff.reduce((M, key) => {
M[key] = $old[key];
return M;
}, {});
//* prepare next-handler's param & body.
const param = { region, eventId, eventName, tableName };
const body = { keys: $key, diff, prev, node };
//* call all listeners in parrallel.
const asyncNext = (fn, j) => new Promise(resolve => {
resolve(fn('!', param, body, context));
}).catch(e => $doReportError(e, null, null, { record, i, j }));
const res = yield Promise.all(this.listeners.map(asyncNext));
(0, engine_1._log)(NS, `>> result[${i}] =`, engine_1.$U.json(res));
return `${i}`;
});
//* serialize all record...
this.$lastResult = yield (0, engine_1.do_parrallel)(records, (record, i) => onStreamRecord(record, i).catch(e => $doReportError(e, null, null, { record, i })), 1);
});
// _log(NS, `LambdaDynamoStreamHandler()..`);
}
/**
* add listener of cron-event.
* @param handler
*/
addListener(handler) {
this.listeners.push(handler);
}
/**
* create synchronizer to elastic6 via dynamo-stream.
* - procedure: (filter) -> (onBeforeSync) -> synchronization -> (onAfterSync)
*
* @param options options of dynamo table.
* @param service Elastic6Service instance
* @param filter filter function
* @param onBeforeSync callback function invoked before synchronization
* @param onAfterSync callback function invoked after synchronization
*/
static createSyncToElastic6(options, service, filter, onBeforeSync, onAfterSync) {
var _a, _b;
//FIXED - if it has decomposed, then update full set.
const hasDecomposed = !!((_b = (_a = service === null || service === void 0 ? void 0 : service.options) === null || _a === void 0 ? void 0 : _a.autocompleteFields) === null || _b === void 0 ? void 0 : _b.length);
// const _log = console.log;
const handler = (id, param, body, $ctx) => __awaiter(this, void 0, void 0, function* () {
const { tableName, idName } = options;
const { region, eventId, eventName, tableName: eventTable } = param;
if (eventTable != tableName) {
(0, engine_1._log)(NS, `WARN! table[${tableName}] is not matched with table[${eventTable}] @${region}`);
return;
}
const { keys, diff, prev, node } = body;
(0, engine_1._log)(NS, `! sync[${eventId}].event =`, eventName);
keys && (0, engine_1._log)(NS, `> keys =`, engine_1.$U.json(keys));
diff && (0, engine_1._log)(NS, `> diff =`, engine_1.$U.json(diff));
prev && (0, engine_1._log)(NS, `> prev =`, engine_1.$U.json(prev));
// node && _log(NS, `> node =`, $U.json(node));
//* find id.
const _id = (node && node[idName]) || keys[idName];
if (!_id) {
node && (0, engine_1._log)(NS, `> node =`, engine_1.$U.json(node));
(0, engine_1._log)(NS, `WARN! node[${_id}] is missing! keys =`, engine_1.$U.json(keys));
return;
}
//* origin object, and apply filter.
const item = engine_1.$U.cleanup(Object.assign({}, node)); //* remove internals like '_' '$'.
const passed = !filter ? true : filter(_id, item, diff, prev);
if (passed !== true && passed !== undefined) {
(0, engine_1._log)(NS, `WARN! node[${_id}] is by-passed`);
return;
}
//* call pre sync function
if (onBeforeSync)
yield onBeforeSync(_id, eventName, item, diff, prev);
//* update or save.
if (false) {
}
else if (eventName == 'REMOVE') {
//* clear data.
const res = yield service.deleteItem(_id); // ignore error.
(0, engine_1._log)(NS, `> deleted[${_id}] =`, engine_1.$U.json(res));
}
else if (hasDecomposed) {
//* overwrite all.
const res = yield service.saveItem(_id, item);
(0, engine_1._log)(NS, `> saved[${_id}] @1 =`, engine_1.$U.json(res));
}
else if (diff && Array.isArray(diff) && diff.length) {
//* try to update in advance, then save.
const $upt = diff.reduce((M, key) => {
M[key] = item[key];
return M;
}, {});
(0, engine_1._log)(NS, `> updates[${_id}] @1 =`, engine_1.$U.json($upt));
const res = yield service.updateItem(_id, $upt).catch((e) => {
if (`${e.message}`.startsWith('404 NOT FOUND'))
return service.saveItem(_id, item);
throw e;
});
(0, engine_1._log)(NS, `> updated[${_id}] @2 =`, engine_1.$U.json(res));
}
else {
//* overwrite all.
const res = yield service.saveItem(_id, item);
(0, engine_1._log)(NS, `> saved[${_id}] @2 =`, engine_1.$U.json(res));
}
//* call post sync function
if (onAfterSync)
yield onAfterSync(_id, eventName, item, diff, prev);
});
return handler;
}
}
exports.LambdaDynamoStreamHandler = LambdaDynamoStreamHandler;
//* shared config.
LambdaDynamoStreamHandler.REPORT_ERROR = lambda_handler_1.LambdaHandler.REPORT_ERROR;
//# sourceMappingURL=lambda-dynamo-stream-handler.js.map
;