lemon-core
Version:
Lemon Serverless Micro-Service Platform
930 lines • 36.6 kB
JavaScript
"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(` 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(` 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(` 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(` 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(` (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(` (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(` (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-service) is required!`);
if (!dynamoOptions)
throw new Error(` (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