UNPKG

lemon-core

Version:
930 lines 36.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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 __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.$ES6 = exports.sourceToItem = exports.Elastic6Instance = exports.Elastic6Synchronizer = exports.AbstractProxy = exports.ManagerProxy = exports.CoreManager = exports.CoreService = exports.filterFields = exports.asIdentityId = void 0; /** * `abstract-service.ts` * - common service design pattern to build micro-service backend. * * @author Tim Hong <tim@lemoncloud.io> * @date 2021-02-23 initial version * @author Steve <steve@lemoncloud.io> * @date 2022-02-18 optimized search w/ ES6.8 * @date 2022-02-22 optimized w/ `lemon-core#3.0` and `@elastic/elasticsearch` * @date 2022-02-24 use `$id` in elastic-search as `_id` in dynamo-table. * @date 2022-03-15 optimized w/ `AbstractProxy` * @date 2022-03-17 optimized w/ `lemon-core#3.0.2` and use `env.ES6_DOCTYPE` * @date 2022-03-31 optimized w/ unit test spec. * @date 2022-05-19 optimized `CacheService` w/ typed key. * * @origin see `lemon-accounts-api/src/service/core-service.ts` * @copyright (C) 2021 LemonCloud Co Ltd. - All Rights Reserved. */ const cores_1 = __importStar(require("../cores/")); const engine_1 = require("../engine/"); const test_helper_1 = require("../common/test-helper"); const helpers_1 = require("../helpers"); const NS = engine_1.$U.NS('back', 'blue'); // NAMESPACE TO BE PRINTED. /** * authentication helper - get identity-id from context * @param context the current request context. */ function asIdentityId(context) { var _a; return (_a = context === null || context === void 0 ? void 0 : context.identity) === null || _a === void 0 ? void 0 : _a.identityId; } exports.asIdentityId = asIdentityId; /** * extract field names from models * - only fields start with lowercase, or all upper. */ const filterFields = (fields, base = []) => fields .filter(field => /^[a-z]+/.test(field) || /^[A-Z_]+$/.test(field)) .reduce((L, k) => { if (k && !L.includes(k)) L.push(k); return L; }, [...base]); exports.filterFields = filterFields; /** * abstract class `CoreService` * - common abstract to build user service * * @abstract */ class CoreService extends cores_1.GeneralKeyMaker { /** * constructor * @param tableName target table-name (or .yml dummy file-name) * @param ns namespace of dataset * @param idName must be `_id` unless otherwise */ constructor(tableName, ns, idName) { super(ns || engine_1.$U.env('NS', 'TT')); /** (optional) current timestamp */ this.current = 0; // for unit-test. set the current time-stamp. /** * override current time */ this.setCurrent = (current) => (this.current = current); this.tableName = tableName || engine_1.$U.env('MY_DYNAMO_TABLE', 'Test'); this.idName = idName || '_id'; } /** * get the current dynamo-options. */ get dynamoOptions() { return { tableName: this.tableName, idName: this.idName, }; } /** * create storage-service w/ fields list. */ makeStorageService(type, fields, filter) { //! use proxy-storage-service for both dynamo-table and dummy-data. const storage = new cores_1.ProxyStorageService(this, this.tableName, fields, filter, this.idName); storage.setTimer(() => (this.current ? this.current : new Date().getTime())); return storage.makeTypedStorageService(type); } } exports.CoreService = CoreService; /** * class: `CoreManager` * - shared core manager for all model * * @abstract */ class CoreManager extends cores_1.AbstractManager { /** * constructor * @protected */ constructor(type, parent, fields, uniqueField) { super(type, parent, fields, uniqueField); /** * say hello() */ this.hello = () => `${this.storage.hello()}`; } /** * get existence of model * @param id */ exists(id) { return __awaiter(this, void 0, void 0, function* () { return (yield this.find(id)) !== null; }); } /** * find model - retrieve or null * @param id model-id */ find(id) { return __awaiter(this, void 0, void 0, function* () { return this.retrieve(id).catch(e => { if ((0, test_helper_1.GETERR)(e).startsWith('404 NOT FOUND')) return null; throw e; }); }); } /** * get model by key * @param key global id(like primary-key) */ findByKey(key) { return __awaiter(this, void 0, void 0, function* () { return this.storage.storage.read(key).catch(e => { if ((0, test_helper_1.GETERR)(e).startsWith('404 NOT FOUND')) return null; throw e; }); }); } /** * batch get models * - retrieve multi models per each id * - must be matched with idList in sequence order. * * @param idList list of id * @param parrallel (optional) in parrallel size */ getMulti(idList, parrallel) { return __awaiter(this, void 0, void 0, function* () { const $map = yield this.getMulti$(idList, 'id', parrallel); return idList.map(id => { var _a; return (_a = $map[id]) !== null && _a !== void 0 ? _a : null; }).map(N => { var _a; return (((_a = N === null || N === void 0 ? void 0 : N.error) === null || _a === void 0 ? void 0 : _a.startsWith('404 NOT FOUND')) ? null : N); }); }); } /** * batch get models in map by idName */ getMulti$(idList, idName = 'id', parrallel) { return __awaiter(this, void 0, void 0, function* () { // 1. find items in unique const ids = idList.reduce((L, id) => { if (id && !L.includes(id)) L.push(id); return L; }, []); // 2. find from storage. const list = yield (0, helpers_1.my_parrallel)(ids.map(id => ({ id })), N => this.retrieve(N.id), parrallel); // 3. convert to map return helpers_1.$T.asMap(list, idName); }); } /** * get by unique field value * @param uniqueValue */ getByUniqueField(uniqueValue) { return __awaiter(this, void 0, void 0, function* () { return this.$unique.findOrCreate(uniqueValue); }); } /** * find model by unique field value - retrieve or null * @param uniqueValue */ findByUniqueField(uniqueValue) { return __awaiter(this, void 0, void 0, function* () { return this.getByUniqueField(uniqueValue).catch(e => { if ((0, test_helper_1.GETERR)(e).startsWith('404 NOT FOUND')) return null; throw e; }); }); } /** * prepare model * - override `AbstractManager.prepare()` */ prepare(id, $def, isCreate = true) { return __awaiter(this, void 0, void 0, function* () { const $org = yield this.find(id); if ($org) return $org; if (!isCreate || $def === undefined) throw new Error(`404 NOT FOUND - ${this.type}:${id}`); const model = this.prepareDefault($def); // create or update lookup if (this.$unique) yield this.updateLookup(id, model, $org); // save target model const $saved = yield this.storage.save(id, Object.assign(Object.assign({}, model), { id })); return Object.assign(Object.assign(Object.assign({}, model), $saved), { id }); }); } /** * update model * - override 'AbstractManager.insert()' * * @deprecated use `AbstractProxy` */ insert(model, initSeq) { return __awaiter(this, void 0, void 0, function* () { const id = helpers_1.$T.S(yield this.storage.storage.nextSeq(this.type, initSeq)); return this.save(id, model); }); } /** * create or update model * @param id model id * @param model model data */ save(id, model) { return __awaiter(this, void 0, void 0, function* () { if (!id) throw new Error(`@id is requird - save()`); const $org = yield this.find(id); if (!$org) model = this.prepareDefault(model); // create or update lookup if (this.$unique) yield this.updateLookup(id, model, $org); // save target model const $saved = yield this.storage.save(id, model); return Object.assign(Object.assign(Object.assign({}, $org), $saved), { id }); }); } /** * update model * - override 'AbstractManager.update()' */ update(id, model, $inc) { return __awaiter(this, void 0, void 0, function* () { if (!id) throw new Error(`@id is requird - update()`); const $org = yield this.retrieve(id); // update lookup if (this.$unique) yield this.updateLookup(id, model, $org); // update target model model = this.beforeSave(model, $org); const $updated = yield this.storage.update(id, model, $inc); return Object.assign(Object.assign(Object.assign({}, $org), $updated), { id }); }); } /** * update or create model * - override 'AbstractManager.updateOrCreate()' */ updateOrCreate(id, model, $inc) { return __awaiter(this, void 0, void 0, function* () { if (!id) throw new Error(`@id is requird - updateOrCreate()`); const $org = yield this.prepare(id, model); // update lookup if (this.$unique) yield this.updateLookup(id, model, $org); // update target model model = this.beforeSave(model, $org); const $updated = yield this.storage.update(id, model, $inc); return Object.assign(Object.assign(Object.assign({}, $org), $updated), { id }); }); } /** * delete model * - override 'AbstractManager.delete()' */ delete(id, destroy) { const _super = Object.create(null, { delete: { get: () => super.delete } }); var _a; return __awaiter(this, void 0, void 0, function* () { if (!id) throw new Error(`@id is requird - delete()`); // delete target model const $org = yield _super.delete.call(this, id, destroy); // delete lookup const uniqueField = (_a = this.$unique) === null || _a === void 0 ? void 0 : _a.field; const uniqueValue = uniqueField && $org[uniqueField]; if (uniqueValue) yield this.storage.delete(this.$unique.asLookupId(uniqueValue)); return $org; }); } /** * prepare default-model when creation * @param $def base-model */ prepareDefault($def) { return Object.assign({}, $def); } /** * update lookup and delete old one if exists */ updateLookup(id, model, $org) { var _a; return __awaiter(this, void 0, void 0, function* () { const uniqueField = (_a = this.$unique) === null || _a === void 0 ? void 0 : _a.field; const newUniqueValue = uniqueField && model[uniqueField]; const oldUniqueValue = uniqueField && ($org === null || $org === void 0 ? void 0 : $org[uniqueField]); // update lookup. if (newUniqueValue && newUniqueValue !== oldUniqueValue) { yield this.$unique.updateLookup(Object.assign({ id }, model)); // remove old lookup if (oldUniqueValue) { yield this.storage.delete(this.$unique.asLookupId(oldUniqueValue)); } } }); } } exports.CoreManager = CoreManager; /** * proxy of manager * - save model internally, and update only if changed properties. * Model extends CoreModel<ModelType>, ModelType extends string */ // export class ManagerProxy<T, U extends CoreManager<T, any, any>> { class ManagerProxy { constructor(proxy, mgr) { /** * store the origin model. * - `null` means `404 not found` */ this._org = {}; /** * store the updated one. */ this._new = {}; /** * 객체 정규화 시킴. * - null 에 대해서는 특별히 처리. */ this.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; }, {}); /** * override w/ model * @param $org the origin model by `.get(id)` * @param model the new model. */ this.override = ($org, model) => { const fields = this.$mgr.FIELDS; //! update(set) all properties. Object.entries(model).forEach(([key, val]) => { if (!fields || fields.includes(key)) { $org[key] = val; } }); return $org; }; this.$mgr = mgr; proxy.register(this); } /** * get storage linked. */ get storage() { return this.$mgr.storage; } /** * read the origin node (cloned not to change). */ org(id, raw = false) { const O = this._org[id]; return O === undefined ? null : raw ? O : Object.assign({}, O); } /** * check if already read. */ has(id) { return this.org(id) ? true : false; } /** * read the node. * @param id object-id * @param defaultOrThrow (optional) create if not exists, or flag to throw error */ get(id, defaultOrThrow) { return __awaiter(this, void 0, void 0, function* () { const err404 = `404 NOT FOUND - proxy/${this.$mgr.type}/id:${id}`; const throwable = typeof defaultOrThrow === 'boolean' ? defaultOrThrow : true; const $def = typeof defaultOrThrow === 'boolean' ? null : defaultOrThrow; // STEP.0 validate if null (404 error) if (this._org[id] === null && !$def) { if (throwable) throw new Error(err404); return null; } // STEP.1 find from `new` const N = this._new[id]; if (N !== undefined) return N; // OR, READ (or CREATE) from storage. const M = !$def ? yield this.$mgr.retrieve(id).catch(test_helper_1.NUL404) : yield this.$mgr.prepare(id, $def, true); if (M === null) { this._org[id] = null; //! null 로 저장해두고, 다음에 호출할때 에러 발생. if (throwable) throw new Error(err404); return null; } const M2 = this.normal(M); this._org[id] = M2; //! 원본 저장. // const M3 = { ...M2 }; //! 클론 생성. const M3 = JSON.parse(engine_1.$U.json(M2)); //! deep clone. this._new[id] = M3; //! 클론 저장. return M3; }); } /** * update the node. */ set(id, model) { return __awaiter(this, void 0, void 0, function* () { if (!model) throw new Error(`@model (object) is required - proxy/${this.$mgr.type}/id:${id}!`); const O = yield this.get(id); return this.override(O, model); }); } /** * increment the field of Object[id] * !WARN! this incremented properties should NOT be updated later. */ inc(id, model) { return __awaiter(this, void 0, void 0, function* () { if (!model) throw new Error(`@model (object) is required - proxy/${this.$mgr.type}/id:${id}!`); const $inc = Object.entries(model).reduce((M, [k, v]) => { if (typeof v === 'number') M[k] = v; return M; }, {}); const keys = Object.keys($inc); if (!keys.length) throw new Error(`@model (object) is empty to inc() - proxy/${this.$mgr.type}/id:${id}!`); //! try to increment, and update the latest to both org and new. const $res = yield this.$mgr.storage.update(id, null, $inc); const $new = yield this.get(id); const $org = this.org(id, true); return keys.reduce((N, k) => { const key = k; N[key] = $org[key] = $res[key]; return N; }, $new); }); } /** * get all the updated node. * * @param onlyUpdated flag to return the only updated set. (useful to check whether to update really!) */ alls(onlyUpdated = true, onlyValid = true) { const ids = Object.keys(this._new).sort(); return ids.reduce((M, id) => { const O = this._org[id]; const N = this._new[id]; const N2 = this.$mgr.onBeforeSave(Object.assign({}, N), O); const N3 = onlyUpdated ? helpers_1.$T.diff(O, N2, onlyValid) : N2; M[id] = N3; return M; }, {}); } } exports.ManagerProxy = ManagerProxy; /** * class: `AbstractProxy` * - common abstract based class for Proxy */ class AbstractProxy { /** * constructor of proxy. * @param service user service instance * @param parrallel parrallel count (default 2) * @param cacheScope prefix of cache-key (like `lemon:SS:` or `lemon:SS:user`) */ constructor(context, service, parrallel = 2, cacheScope) { /** * say hello(). */ this.hello = () => `manager-proxy:${this.service.NS}/${this.service.tableName}`; /** * list of manager-proxy */ this._proxies = []; /** * report via slack. */ this.report = (title, data) => __awaiter(this, void 0, void 0, function* () { const context = this.context; return (0, helpers_1.$slack)(title, data, null, { context }).catch(e => `ERR:${(0, test_helper_1.GETERR)(e)}`); }); /** * the cached identity model */ this._identity = {}; this.context = context; this.service = service; this.parrallel = parrallel; // create cache const endpoint = engine_1.$U.env('CACHE_ENDPOINT', ''); if (cacheScope && endpoint.startsWith('redis:')) { this.cache = cores_1.CacheService.create({ type: 'redis', endpoint, ns: cacheScope }); } } /** * get all proxies in list. */ get allProxies() { return this._proxies; } /** * register this. * * @return size of proxies. */ register(mgr) { this._proxies.push(mgr); return this._proxies.length; } /** * save all updates by each proxies. * - 업데이트할 항목을 모두 저장함 * * @param options running parameters. */ saveAllUpdates(options) { return __awaiter(this, void 0, void 0, function* () { const parrallel = engine_1.$U.N(options === null || options === void 0 ? void 0 : options.parrallel, this.parrallel); // STEP.1 prepare the list of updater. const list = this.allProxies.reduce((L, $p) => { const $set = $p.alls(true, options === null || options === void 0 ? void 0 : options.onlyValid); return Object.entries($set).reduce((L, [id, N]) => { const hasUpdate = Object.keys(N).length > 0; if (hasUpdate) { (0, engine_1._log)(NS, `>> ${$p.$mgr.type}/${id} =`, engine_1.$U.json(N)); const _ = () => $p.$mgr.storage.update(id, N); L.push({ id, N, _ }); } return L; }, L); }, []); // STEP.2 finally update storage. return (0, helpers_1.my_parrallel)(list, (N) => __awaiter(this, void 0, void 0, function* () { return typeof N._ === 'function' ? N._() : null; }), parrallel); }); } /** * featch identity-acess from `lemon-accounts-api` */ fetchIdentityAccess(identityId, domain) { return __awaiter(this, void 0, void 0, function* () { domain = helpers_1.$T.S(domain, this.context.domain); if (!identityId) throw new Error(`.identityId (string) is required - fetchAccess(${domain})`); // 1. get user detail by invoking 'lemon-accounts-api/pack-context' const service = '//lemon-accounts-api/oauth/0/pack-context'; const body = { domain, identityId }; const $identity = yield (0, helpers_1.$protocol)(this.context, service) .execute({}, body, 'POST') .catch(test_helper_1.NUL404); (0, engine_1._log)(NS, `> identity[${domain}] =`, engine_1.$U.json($identity)); //WARN! - $identity can be null (or .Account can be null) // if (!$identity?.Account) // throw new Error(`.Account(NextIdentityAccess) is invalid - fetchAccess(${domain}/${identityId})`); return { identityId, $identity }; }); } /** * fetch(or load) identity. * * @param identityId id to find * @param force (optional) force to reload if not available * @returns the cached identity-access */ getIdentity$(identityId, force) { return __awaiter(this, void 0, void 0, function* () { if (!identityId) return null; // STEP.1 check if in stock. const val = this._identity[identityId]; if (val !== undefined && !force) return val; // STEP.2 fetch remotely, and save in cache. const { $identity } = yield this.fetchIdentityAccess(identityId); this._identity[identityId] = $identity ? $identity : null; //! mark as 'null' not to fetch futher return $identity; }); } /** * get current identity-id */ getCurrentIdentityId(throwable = true) { return __awaiter(this, void 0, void 0, function* () { const identityId = asIdentityId(this.context) || ''; if (!identityId && throwable) throw new Error(`400 NOT ALLOWED - getCurrentIdentity(${identityId || ''})`); return identityId; }); } /** * get the current identity object (or throw access-error) */ getCurrentIdentity$(throwable = true) { return __awaiter(this, void 0, void 0, function* () { const identityId = yield this.getCurrentIdentityId(throwable); if (!identityId && !throwable) return null; return this.getIdentity$(identityId); }); } } exports.AbstractProxy = AbstractProxy; /** * class `Elastic6Synchronizer` * - listen DynamoDBStream events and index into Elasticsearch */ class Elastic6Synchronizer { /** * constructor * @param elastic elastic6-service instance * @param dynamoOptions dynamo options */ constructor(elastic, dynamoOptions) { /** * internal callback for filtering * @private */ this.filter = (id, item) => { var _a, _b; const handler = this.synchronizerMap.get(item.type); if (handler) return (_b = (_a = handler.filter) === null || _a === void 0 ? void 0 : _a.call(handler, id, item)) !== null && _b !== void 0 ? _b : true; // 핸들러를 등록했다면 filter 메서드를 정의하지 않았더라도 sync 한다. return false; // 핸들러가 등록되지 않았다면 sync하지 않는다. }; /** * internal callback on before synchronization * @private */ this.onBeforeSync = (id, eventName, item, diff, prev) => __awaiter(this, void 0, void 0, function* () { var _a; const handler = this.synchronizerMap.get(item.type); if (handler) yield ((_a = handler.onBeforeSync) === null || _a === void 0 ? void 0 : _a.call(handler, id, eventName, item, diff, prev)); }); /** * internal callback on after synchronization * @private */ this.onAfterSync = (id, eventName, item, diff, prev) => __awaiter(this, void 0, void 0, function* () { var _b; const handler = this.synchronizerMap.get(item.type); if (handler) yield ((_b = handler.onAfterSync) === null || _b === void 0 ? void 0 : _b.call(handler, id, eventName, item, diff, prev)); }); if (!elastic) throw new Error(`@elastic (elastic-service) is required!`); if (!dynamoOptions) throw new Error(`@dynamoOptions (object) is required!`); //! build dynamo-options as default. const options = Object.assign(Object.assign({}, dynamoOptions), { idName: dynamoOptions.idName || '_id' }); //! create sync-handler w/ this. const listener = cores_1.LambdaDynamoStreamHandler.createSyncToElastic6(options, elastic, this.filter.bind(this), this.onBeforeSync.bind(this), this.onAfterSync.bind(this)); cores_1.default.lambda.dynamos.addListener(listener); // register DynamoStream event listener //! prepare default synchro this.synchronizerMap = new Map(); this.defModelSynchronizer = new (class { filter(id, item) { const type = `${(item === null || item === void 0 ? void 0 : item.type) || ''}`; const stereo = `${(item === null || item === void 0 ? void 0 : item.stereo) || ''}`; return !(type.startsWith('#') || stereo.startsWith('#')); // special purpose item. do not index. } })(); } /** * set synchronizer for the model * @param type the model-type * @param handler (optional) custom synchronizer. */ enableSynchronization(type, handler) { this.synchronizerMap.set(type, handler !== null && handler !== void 0 ? handler : this.defModelSynchronizer); } } exports.Elastic6Synchronizer = Elastic6Synchronizer; /** * class `ElasticInstance` * - to manipulate the shared Elasticsearch resources. */ class Elastic6Instance { /** * default constructor */ constructor({ endpoint, indexName, esVersion, esDocType, tableName, autocompleteFields, }) { // const endpoint = $U.env('ES6_ENDPOINT', ''); // const indexName = $U.env('ES6_INDEX', 'test-v1'); // const esVersion = $U.env('ES6_VERSION', '6.8'); //! version of elastic server (default 6.8) // const esDocType = $U.env('ES6_DOCTYPE', ''); //! version of elastic server (default `_doc`) // const tableName = $U.env('MY_DYNAMO_TABLE', 'Test'); // const autocompleteFields = $T.SS($U.env('ES6_AUTOCOMPLETE_FIELDS', '')); // initialize Elasticsearch only if valid endpoint. if (endpoint && indexName) { const options = { endpoint, indexName, version: esVersion, autocompleteFields, }; if (esDocType) options.docType = esDocType; this.elastic = new cores_1.Elastic6Service(options); this.client = this.elastic.client; this.query = new cores_1.Elastic6QueryService(this.elastic); this.synchronizer = new Elastic6Synchronizer(this.elastic, { tableName }); } } /** * read the current elastic6-option. */ get options() { if (!this.elastic) return null; return this.elastic.options; } /** * create Elasticsearch index w/ custom settings */ createIndex() { var _a, _b; return __awaiter(this, void 0, void 0, function* () { if (this.elastic) { const { docType, idName } = this.options; const settings = cores_1.Elastic6Service.prepareSettings({ docType, idName }); // default setting const { version } = this.elastic.options; // force set type of 'score_' field const ver = engine_1.$U.F(version, 7.0); const $set = { score_: { type: 'half_float' } }; if (ver < 7.0) { settings.mappings[docType].properties = Object.assign(Object.assign({}, (((_a = settings.mappings[docType]) === null || _a === void 0 ? void 0 : _a.properties) || {})), $set); } else { settings.mappings.properties = Object.assign(Object.assign({}, (((_b = settings.mappings) === null || _b === void 0 ? void 0 : _b.properties) || {})), $set); } return this.elastic.createIndex(settings); } return null; }); } /** * destroy Elasticsearch index */ destroyIndex() { return __awaiter(this, void 0, void 0, function* () { return this.elastic && (yield this.elastic.destroyIndex()); }); } /** * display index settings and mappings */ describeIndex() { return __awaiter(this, void 0, void 0, function* () { return this.elastic && (yield this.elastic.describe()); }); } /** * multi get * @param _ids _id list */ mget(_ids) { return __awaiter(this, void 0, void 0, function* () { const $res = yield this.client.mget({ index: this.options.indexName, type: this.options.docType, body: { docs: _ids.map(_id => ({ _id })), }, }); // _log(NS, `> res =`, $U.json({ ...$res, meta: undefined })); const { docs } = $res.body; const idName = this.options.idName; return docs.map((doc) => (doc.found ? sourceToItem(doc._source, idName) : null)); }); } /** * search raw query * @param body Elasticsearch Query DSL * @param params see 'search_type' in Elasticsearch documentation */ search(body, params) { return __awaiter(this, void 0, void 0, function* () { if (!this.elastic) throw new Error(`Could not read Elasticsearch endpoint or index setting.`); const searchType = params === null || params === void 0 ? void 0 : params.searchType; const elastic = (params === null || params === void 0 ? void 0 : params.indexName) ? new cores_1.Elastic6Service(Object.assign(Object.assign({}, this.options), { indexName: params.indexName })) : this.elastic; return elastic.search(body, searchType); }); } /** * create async generator that yields items queried until last * @param body Elasticsearch Query DSL * @param searchType see 'search_type' in Elasticsearch documentation */ generateSearchResult(body, searchType) { return __asyncGenerator(this, arguments, function* generateSearchResult_1() { if (!body.sort) body.sort = '_doc'; do { const { list, last } = yield __await(this.search(body, { searchType })); body.search_after = last; yield yield __await(list); } while (body.search_after); }); } } exports.Elastic6Instance = Elastic6Instance; /** * from Elasticsearch document to model item * - replace the elastic's `$id` field to `_id` of dynamo-table. * * @param _source from elastic-search * @param idName (optional) global id of elastic. (default is `$id`) */ function sourceToItem(_source, idName = '$id') { const item = Object.assign({}, _source); if (idName in item) { item._id = item[idName]; delete item[idName]; } return item; } exports.sourceToItem = sourceToItem; /** * const `$ES6` * - default instance as a singleton by env configuration. */ exports.$ES6 = new (class extends Elastic6Instance { constructor() { // 0. load from env configuration. const endpoint = engine_1.$U.env('ES6_ENDPOINT', ''); const indexName = engine_1.$U.env('ES6_INDEX', 'test-v1'); const esVersion = engine_1.$U.env('ES6_VERSION', '6.8'); //! version of elastic server (default 6.8) const esDocType = engine_1.$U.env('ES6_DOCTYPE', ''); //! version of elastic server (default `_doc`) const tableName = engine_1.$U.env('MY_DYNAMO_TABLE', 'Test'); const autocompleteFields = helpers_1.$T.SS(engine_1.$U.env('ES6_AUTOCOMPLETE_FIELDS', '')); // 1. initialize instance. super({ endpoint, indexName, esVersion, esDocType, tableName, autocompleteFields, }); } })(); //# sourceMappingURL=abstract-service.js.map