UNPKG

lemon-core

Version:
179 lines 9.36 kB
"use strict"; 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