UNPKG

lemon-core

Version:
1,236 lines 59.9 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());
    });
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var m = o[Symbol.asyncIterator], i;
    return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
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]); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DummyElastic6Service = exports.$ERROR = exports.Elastic6Service = exports.ElasticIndexService = exports.$hangul = exports.elasticsearch = void 0;
/**
 * `elastic6-service.ts`
 * - common service for elastic-search v6
 *
 * @author      Steve Jung <steve@lemoncloud.io>
 * @date        2019-11-20 initial version via backbone
 * @date        2022-02-21 optimized error handler, and search.
 * @date        2022-02-22 optimized w/ elastic client (elasticsearch-js)
 *
 * @copyright (C) 2019 LemonCloud Co Ltd. - All Rights Reserved.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const engine_1 = require("../../engine/");
const elasticsearch_1 = __importDefault(require("@elastic/elasticsearch"));
exports.elasticsearch = elasticsearch_1.default;
const hangul_service_1 = __importDefault(require("./hangul-service"));
exports.$hangul = hangul_service_1.default;
const tools_1 = require("../../tools");
const test_helper_1 = require("../../common/test-helper");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const NS = engine_1.$U.NS('ES6', 'green'); // NAMESPACE TO BE PRINTED.
/**
 * convert to string.
 */
const _S = (v, def = '') => typeof v === 'string' ? v : v === undefined || v === null ? def : typeof v === 'object' ? engine_1.$U.json(v) : `${v}`;
/** ****************************************************************************************************************
 *  Elastic Index Service
 ** ****************************************************************************************************************/
/**
 * abstarct class: `ElasticIndexService`
 * - abstract class for basic Elasticsearch CRUD operations
 * - common operations that are shared across different versions.
 * TODO - support `Elastic` and `OpenSearch`
 */
class ElasticIndexService {
    /**
     * default constuctor w/ options.
     * @param options { endpoint, indexName } is required.
     */
    constructor(options) {
        (0, engine_1._inf)(NS, `ElasticIndexService(${options.indexName}/${options.idName})...`);
        if (!options.endpoint)
            throw new Error('.endpoint (URL) is required');
        if (!options.indexName)
            throw new Error('.indexName (string) is required');
        // default option values: docType='_doc', idName='$id'
        const { client } = ElasticIndexService.instance(options.endpoint);
        this._options = Object.assign({ docType: '_doc', idName: '$id', version: '6.8' }, options);
        this._client = client;
    }
    /**
     * simple instance maker.
     *
     * ```js
     * const { client } = ElasticIndexService.instance(endpoint);
     * ```
     *
     * @param endpoint  service-url
     * @see https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/16.x/configuration.html
     */
    static instance(endpoint) {
        const client = new elasticsearch_1.default.Client({
            node: endpoint,
            ssl: {
                ca: process.env.elasticsearch_certificate,
                rejectUnauthorized: false,
            },
        });
        return { client };
    }
    /**
     * get the client instance.
     */
    get client() {
        return this._client;
    }
    /**
     * get the current options.
     */
    get options() {
        return this._options;
    }
    /**
     * get the version from options
     */
    get version() {
        const ver = engine_1.$U.F(this.options.version, 6.8);
        return ver;
    }
}
exports.ElasticIndexService = ElasticIndexService;
/**
 * class: `Elastic6Service`
 * - extends `ElasticIndexService` and adds version-specific implementation
 */
class Elastic6Service extends ElasticIndexService {
    /**
     * default constuctor w/ options.
     * @param options { endpoint, indexName } is required.
     */
    constructor(options) {
        super(options);
        /**
         * say hello of identity.
         */
        this.hello = () => `elastic6-service:${this.options.indexName}:${this.options.version}`;
        (0, engine_1._inf)('Elastic6Service', `Elastic6Service(${options.indexName}/${options.idName})...`);
    }
    /**
     * get isOldES6
     * - used when setting doctype
     * - used when verifying mismatched error and results of search
     */
    get isOldES6() {
        return this.parsedVersion.major < 7 && this.parsedVersion.engine === 'es';
    }
    /**
     * get isOldES71
     * - used when verifying mismatched error
     */
    get isOldES71() {
        return this.parsedVersion.major == 7 && this.parsedVersion.minor == 1 && this.parsedVersion.engine === 'es';
    }
    /**
     * get isLatestOS2
     * - used when verifying results of search
     */
    get isLatestOS2() {
        return this.parsedVersion.major >= 2 && this.parsedVersion.engine === 'os';
    }
    /**
     * get the parsedVersion
     */
    get parsedVersion() {
        return this.parseVersion(this.options.version);
    }
    /**
     * get the root version from client
     *
     * @protected only for internal test.
     */
    getVersion(options) {
        var _a, _b, _c;
        return __awaiter(this, void 0, void 0, function* () {
            const isDump = (_a = options === null || options === void 0 ? void 0 : options.dump) !== null && _a !== void 0 ? _a : false;
            // it consumes about >20ms
            const info = yield this.client.info();
            const rootVersion = engine_1.$U.S(info.body.version.number);
            const parsedVersion = this.parseVersion(rootVersion, { throwable: true });
            if (isDump) {
                //* save into `info.json`.
                const description = Object.assign({ '!': `${(_b = this.parsedVersion) === null || _b === void 0 ? void 0 : _b.engine}${this.options.version} client info` }, info);
                const filePath = path_1.default.resolve(__dirname, `../../../data/samples/${(_c = this.parsedVersion) === null || _c === void 0 ? void 0 : _c.engine}${this.options.version}/info.json`);
                yield this.saveInfoToFile(description, filePath);
            }
            return parsedVersion;
        });
    }
    /**
     * check whether the service version matches the version provided in the options.
     *
     * @protected only for internal test.
     */
    executeSelfTest() {
        return __awaiter(this, void 0, void 0, function* () {
            // STEP.1 read the parsed-version.
            const optionVersion = this.parsedVersion;
            // STEP.2 get the real version via `getVersion()`
            const rootVersion = yield this.getVersion();
            // STEP.3 validate version
            const isEqual = optionVersion.engine === rootVersion.engine &&
                optionVersion.major === rootVersion.major &&
                optionVersion.minor === rootVersion.minor;
            // Return the comparison result
            return {
                isEqual: isEqual,
                optionVersion: optionVersion,
                rootVersion: rootVersion,
            };
        });
    }
    /**
     * parse version according to Semantic Versioning (SemVer) rules.
     *
     * @param version The version string to parse (e.g., "1.2.3", "1.2.3-alpha.1", "1.2.3+build.001").
     * @param options Optional configuration for throwable behavior.
     * @returns A ParsedVersion object or null if parsing fails and throwable is false.
     */
    parseVersion(version, options) {
        var _a;
        const isThrowable = (_a = options === null || options === void 0 ? void 0 : options.throwable) !== null && _a !== void 0 ? _a : true;
        if (!version && isThrowable)
            throw new Error(`@version (string) is required!`);
        // RegEx to match Semantic Versioning patterns
        const match = version === null || version === void 0 ? void 0 : version.match(/^(\d{1,2})(?:\.(\d{1,2}))?(?:\.(\d{1,2}))?(?:-([a-zA-Z0-9-.]+))?(?:\+([a-zA-Z0-9-.]+))?$/);
        if (!match) {
            if (isThrowable)
                throw new Error(`@version[${version}] is invalid - fail to parse`);
            return null;
        }
        const res = Object.assign(Object.assign({ engine: engine_1.$U.N(match[1], 10) < 6 ? 'os' : 'es', major: engine_1.$U.N(match[1], 10), minor: match[2] !== undefined ? engine_1.$U.N(match[2], 10) : 0, patch: match[3] !== undefined ? engine_1.$U.N(match[3], 10) : 0 }, (match[4] !== undefined ? { prerelease: match[4] } : {})), (match[5] !== undefined ? { build: match[5] } : {}));
        return res;
    }
    /**
     * save info to a JSON file.
     * @param info - The information to be saved
     * @param filePath - The file path where should be saved.
     */
    saveInfoToFile(info, filePath) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const directory = path_1.default.dirname(filePath);
                // check whether directory exists
                if (!fs_1.default.existsSync(directory)) {
                    fs_1.default.mkdirSync(directory, { recursive: true });
                }
                // write info to file
                fs_1.default.writeFileSync(filePath, JSON.stringify(info, null, 2));
            }
            catch (_a) {
                exports.$ERROR.handler('saveIntoFile', e => {
                    throw e;
                });
            }
        });
    }
    /**
     * list of index
     */
    listIndices() {
        return __awaiter(this, void 0, void 0, function* () {
            (0, engine_1._log)(NS, `- listIndices()`);
            //* prepare client..
            const client = this.client;
            const res = yield client.cat.indices({ format: 'json' });
            (0, engine_1._log)(NS, `> indices =`, engine_1.$U.json(res));
            // eslint-disable-next-line prettier/prettier
            const list0 = Array.isArray(res) ? res : (res === null || res === void 0 ? void 0 : res.body) && Array.isArray(res === null || res === void 0 ? void 0 : res.body) ? res === null || res === void 0 ? void 0 : res.body : null;
            if (!list0)
                throw new Error(`@result<${typeof res}> is invalid - ${engine_1.$U.json(res)}!`);
            // {"docs.count": "84", "docs.deleted": "7", "health": "green", "index": "dev-eureka-alarms-v1", "pri": "5", "pri.store.size": "234.3kb", "rep": "1", "status": "open", "store.size": "468.6kb", "uuid": "xPp-Sx86SgmhAWxT3cGAFw"}
            const list = list0.map(N => ({
                pri: engine_1.$U.N(N['pri']),
                rep: engine_1.$U.N(N['rep']),
                docsCount: engine_1.$U.N(N['docs.count']),
                docsDeleted: engine_1.$U.N(N['docs.deleted']),
                health: _S(N['health']),
                index: _S(N['index']),
                status: _S(N['status']),
                uuid: _S(N['uuid']),
                priStoreSize: _S(N['pri.store.size']),
                storeSize: _S(N['store.size']),
            }));
            return { list };
        });
    }
    /**
     * get mapping of an index
     * @param indexName - name of the index
     */
    getIndexMapping() {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            const client = this.client;
            const indexName = this.options.indexName;
            const res = yield client.indices.getMapping({ index: indexName }).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('getMapping', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('404 INDEX NOT FOUND'))
                    throw new Error(`404 NOT FOUND - index:${indexName}`);
                throw e;
            }));
            const mapping = (res === null || res === void 0 ? void 0 : res.body) ? (_a = res.body[indexName]) === null || _a === void 0 ? void 0 : _a.mappings : null;
            if (!mapping)
                throw new Error(`@indexName[${indexName}] is not found - ${engine_1.$U.json(res)}!`);
            return mapping;
        });
    }
    /**
     * find the index by name
     * @param indexName - name of the index
     */
    findIndex(indexName) {
        return __awaiter(this, void 0, void 0, function* () {
            indexName = indexName || this.options.indexName;
            (0, engine_1._log)(NS, `- findIndex(${indexName})`);
            const { list } = yield this.listIndices();
            const found = list.findIndex(N => N.index == indexName);
            return found >= 0 ? list[found] : null;
        });
    }
    /**
     * create index by name
     * @param settings - creating settings
     */
    createIndex(settings) {
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName, docType, idName, timeSeries, version } = this.options;
            settings = settings || Elastic6Service.prepareSettings({ docType, idName, timeSeries, version });
            if (!indexName)
                new Error('@index is required!');
            (0, engine_1._log)(NS, `- createIndex(${indexName})`);
            //* prepare payload
            const payload = Object.assign({ settings: {
                    number_of_shards: 5,
                    number_of_replicas: 1,
                } }, settings);
            (0, engine_1._log)(NS, `> settings[${indexName}] = `, engine_1.$U.json(payload));
            //* call create index..
            const client = this.client;
            const res = yield client.indices.create({ index: indexName, body: payload }).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('create', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('400 RESOURCE ALREADY EXISTS'))
                    throw new Error(`400 IN USE - index:${indexName}`);
                throw e;
            }));
            // if (res) throw res;
            (0, engine_1._log)(NS, `> create[${indexName}] =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            //* build result.
            return {
                status: res.statusCode,
                index: indexName,
                acknowledged: res.body.shards_acknowledged,
            };
        });
    }
    /**
     * destroy search index
     */
    destroyIndex() {
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName } = this.options;
            if (!indexName)
                new Error('@index is required!');
            (0, engine_1._log)(NS, `- destroyIndex(${indexName})`);
            //* call destroy index..
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.indices.delete({ index: indexName }).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('destroy', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('404 INDEX NOT FOUND'))
                    throw new Error(`404 NOT FOUND - index:${indexName}`);
                throw e;
            }));
            // if (res) throw res;
            (0, engine_1._log)(NS, `> destroy[${indexName}] =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            return {
                status: res.statusCode,
                index: indexName,
                acknowledged: res.body.acknowledged,
            };
        });
    }
    /**
     * refresh search index - refresh index to make all items searchable
     */
    refreshIndex() {
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName } = this.options;
            if (!indexName)
                throw new Error('.indexName is required!');
            (0, engine_1._log)(NS, `- refreshIndex(${indexName})`);
            //* call refresh index..
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.indices.refresh({ index: indexName }).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('refresh', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('404 INDEX NOT FOUND'))
                    throw new Error(`404 NOT FOUND - index:${indexName}`);
                throw e;
            }));
            (0, engine_1._log)(NS, `> refresh[${indexName}] =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            return res.body;
        });
    }
    /**
     * flush search index - force store changes into search index immediately
     */
    flushIndex() {
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName } = this.options;
            if (!indexName)
                throw new Error('.indexName is required!');
            (0, engine_1._log)(NS, `- flushIndex(${indexName})`);
            //* call flush index..
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.indices.flush({ index: indexName }).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('flush', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('404 INDEX NOT FOUND'))
                    throw new Error(`404 NOT FOUND - index:${indexName}`);
                throw e;
            }));
            (0, engine_1._log)(NS, `> flush[${indexName}] =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            return res.body;
        });
    }
    /**
     * describe `settings` and `mappings` of index.
     */
    describe() {
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName } = this.options;
            //* call create index..
            (0, engine_1._log)(NS, `- describe(${indexName})`);
            //* read settings.
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.indices.getSettings({ index: indexName }).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('describe', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('404 INDEX NOT FOUND'))
                    throw new Error(`404 NOT FOUND - index:${indexName}`);
                throw e;
            }));
            (0, engine_1._log)(NS, `> settings[${indexName}] =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            const settings = (res.body && res.body[indexName] && res.body[indexName].settings) || {};
            (0, engine_1._log)(NS, `> number_of_shards =`, settings.index && settings.index.number_of_shards); // 5
            (0, engine_1._log)(NS, `> number_of_replicas =`, settings.index && settings.index.number_of_replicas); // 1
            //* read mappings.
            const res2 = yield client.indices.getMapping({ index: indexName });
            (0, engine_1._log)(NS, `> mappings[${indexName}] =`, engine_1.$U.json(res2));
            const mappings = (res2.body && res2.body[indexName] && res2.body[indexName].mappings) || {};
            //* returns
            return { settings, mappings };
        });
    }
    /**
     * save single item
     *
     * @param id - id
     * @param item - item to save
     * @param type - document type (default: doc-type given at construction time)
     */
    saveItem(id, item, type) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName, docType, idName } = this.options;
            (0, engine_1._log)(NS, `- saveItem(${id})`);
            // const { client } = instance(endpoint);
            const client = this.client;
            // prepare item body and autocomplete fields
            const body = Object.assign(Object.assign({}, item), { [idName]: id });
            const body2 = this.popullateAutocompleteFields(body);
            type = `${type || docType}`;
            const params = { index: indexName, id, body: body2 };
            // check version to include 'type' in params
            if (this.isOldES6) {
                params.type = type;
            }
            if (idName === '_id')
                delete params.body[idName]; //WARN! `_id` is reserved in ES6.
            (0, engine_1._log)(NS, `> params[${id}] =`, engine_1.$U.json(params));
            //NOTE - use npm `elasticsearch#13.2.0` for avoiding error.
            const res = yield client.create(params).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('save', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                //* try to overwrite document..
                if (msg.startsWith('409 VERSION CONFLICT ENGINE')) {
                    // delete body2[idName]; // do set id while update
                    // return this.updateItem(id, body2);
                    const param2 = { index: indexName, id, body: Object.assign({}, body2) };
                    if (this.isOldES6)
                        param2.type = type;
                    return client.index(param2);
                }
                throw e;
            }));
            (0, engine_1._log)(NS, `> create[${id}].res =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            const _version = engine_1.$U.N((_a = res.body) === null || _a === void 0 ? void 0 : _a._version, 0);
            const _id = (_b = res.body) === null || _b === void 0 ? void 0 : _b._id;
            const res2 = Object.assign(Object.assign({}, body), { _id, _version });
            return res2;
        });
    }
    /**
     * push item for time-series data.
     *
     * @param item - item to push
     * @param type - document type (default: doc-type given at construction time)
     */
    pushItem(item, type) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName, docType } = this.options;
            const id = '';
            type = `${type || docType}`;
            const body = Object.assign({}, item);
            const body2 = this.popullateAutocompleteFields(body);
            (0, engine_1._log)(NS, `- pushItem(${id})`);
            const params = { index: indexName, type, body: body2 };
            (0, engine_1._log)(NS, `> params[${id}] =`, engine_1.$U.json(params));
            //NOTE - use npm `elasticsearch#13.2.0` for avoiding error.
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.index(params).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('index', e => {
                (0, engine_1._err)(NS, `> index[${indexName}].err =`, e instanceof Error ? e : engine_1.$U.json(e));
                throw e;
            }));
            // {"_index":"test-v3","_type":"_doc","_id":"rTeHiW4BPb_liACrA9qa","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":2,"_primary_term":1}
            (0, engine_1._log)(NS, `> create[${id}].res =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            const _id = (_a = res.body) === null || _a === void 0 ? void 0 : _a._id;
            const _version = (_b = res.body) === null || _b === void 0 ? void 0 : _b._version;
            const res2 = Object.assign(Object.assign({}, body), { _id, _version });
            return res2;
        });
    }
    /**
     * read item with projections
     *
     * @param id - item-id
     * @param views - projections
     */
    readItem(id, views) {
        var _a, _b, _c;
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName, docType } = this.options;
            const type = `${docType}`;
            (0, engine_1._log)(NS, `- readItem(${id})`);
            const params = { index: indexName, type, id };
            if (views) {
                const fields = [];
                const keys = Array.isArray(views) ? views : Object.keys(views);
                keys.forEach((k) => {
                    fields.push(k);
                });
                params._source = fields;
            }
            (0, engine_1._log)(NS, `> params[${id}] =`, engine_1.$U.json(params));
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.get(params).catch(
            // $ERROR.throwAsJson,
            exports.$ERROR.handler('read', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('404 NOT FOUND'))
                    throw new Error(`404 NOT FOUND - id:${id}`);
                if (msg.startsWith('404 INDEX NOT FOUND'))
                    throw new Error(`404 NOT FOUND - index:${indexName}`);
                throw e;
            }));
            (0, engine_1._log)(NS, `> read[${id}].res =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            const _id = (_a = res.body) === null || _a === void 0 ? void 0 : _a._id;
            const _version = (_b = res.body) === null || _b === void 0 ? void 0 : _b._version;
            const data = (res === null || res === void 0 ? void 0 : res._source) || ((_c = res.body) === null || _c === void 0 ? void 0 : _c._source) || {};
            // delete internal (analyzed) field
            delete data[Elastic6Service.DECOMPOSED_FIELD];
            delete data[Elastic6Service.QWERTY_FIELD];
            const res2 = Object.assign(Object.assign({}, data), { _id, _version });
            return res2;
        });
    }
    /**
     * delete item with projections
     *
     * @param id - item-id
     */
    deleteItem(id) {
        var _a, _b, _c, _d;
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName, docType } = this.options;
            const type = `${docType}`;
            (0, engine_1._log)(NS, `- readItem(${id})`);
            const params = { index: indexName, type, id };
            (0, engine_1._log)(NS, `> params[${id}] =`, engine_1.$U.json(params));
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.delete(params).catch(exports.$ERROR.handler('read', e => {
                const msg = (0, test_helper_1.GETERR)(e);
                if (msg.startsWith('404 NOT FOUND'))
                    throw new Error(`404 NOT FOUND - id:${id}`);
                if (msg.startsWith('404 INDEX NOT FOUND'))
                    throw new Error(`404 NOT FOUND - index:${indexName}`);
                throw e;
            }));
            // {"_index":"test-v3","_type":"_doc","_id":"aaa","_version":3,"result":"deleted","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":4,"_primary_term":1}
            (0, engine_1._log)(NS, `> delete[${id}].res =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            const _id = (_a = res.body) === null || _a === void 0 ? void 0 : _a._id;
            const _version = (_b = res.body) === null || _b === void 0 ? void 0 : _b._version;
            const data = ((_c = res.body) === null || _c === void 0 ? void 0 : _c._source) || ((_d = res.body) === null || _d === void 0 ? void 0 : _d._source) || {};
            const res2 = Object.assign(Object.assign({}, data), { _id, _version });
            return res2;
        });
    }
    /**
     * update item (throw if not exist)
     * `update table set a=1, b=b+2 where id='a1'`
     * 0. no of `a1` -> 1,2 (created)
     * 1. a,b := 10,20 -> 11,22
     * 2. a,b := 10,null -> 11,2 (upsert)
     * 3. a,b := null,20 -> 1,22
     *
     * @param id - item-id
     * @param item - item to update
     * @param increments - item to increase
     * @param options - (optional) request option of client.
     */
    updateItem(id, item, increments, options) {
        return __awaiter(this, void 0, void 0, function* () {
            const { indexName, docType, idName } = this.options;
            const type = `${docType}`;
            (0, engine_1._log)(NS, `- updateItem(${id})`);
            item = !item && increments ? undefined : item;
            //* prepare params.
            const params = { index: indexName, id, body: {} };
            // check version to include 'type' in params
            if (this.isOldES6) {
                params.type = type;
            }
            const scripts = [];
            if (increments) {
                //* it will create if not exists.
                params.body.upsert = Object.assign(Object.assign({}, increments), { [idName]: id });
                Object.entries(increments).forEach(([key, val]) => {
                    if (Array.isArray(val)) {
                        // If the value is an array, append it to the existing array in the source
                        scripts.push(`if (ctx._source['${key}'] != null && ctx._source['${key}'] instanceof List) {
                            ctx._source['${key}'].addAll(params.increments['${key}']);
                        } else {
                            ctx._source['${key}'] = params.increments['${key}'];
                        }`);
                    }
                    else {
                        // If the value is a number, increment the existing field
                        scripts.push(`if (ctx._source['${key}'] != null) {
                            ctx._source['${key}'] += params.increments['${key}'];
                        } else {
                            ctx._source['${key}'] = params.increments['${key}'];
                        }`);
                    }
                });
            }
            if (item) {
                // Handle item updates in the script
                Object.entries(item).forEach(([key]) => {
                    scripts.push(`ctx._source['${key}'] = params.item['${key}'];`);
                });
            }
            params.body.script = {
                source: scripts.join(' '),
                lang: 'painless',
                params: { item, increments },
            };
            (0, engine_1._log)(NS, `> params[${id}] =`, engine_1.$U.json(params));
            // const { client } = instance(endpoint);
            const client = this.client;
            const res = yield client.update(params, options).catch(exports.$ERROR.handler('update', (e, E) => {
                const msg = (0, test_helper_1.GETERR)(e);
                //* id 아이템이 없을 경우 발생함.
                if (msg.startsWith('404 DOCUMENT MISSING'))
                    throw new Error(`404 NOT FOUND - id:${id}`);
                //* 해당 속성이 없을때 업데이트 하려면 생길 수 있음.
                if (msg.startsWith('400 REMOTE TRANSPORT'))
                    throw new Error(`400 INVALID FIELD - id:${id}`);
                if (msg.startsWith('404 NOT FOUND'))
                    throw new Error(`404 NOT FOUND - id:${id}`);
                if (msg.startsWith('400 ACTION REQUEST VALIDATION'))
                    throw e;
                if (msg.startsWith('400 INVALID FIELD'))
                    throw e; // at ES6.8
                if (msg.startsWith('400 MAPPER PARSING'))
                    throw e;
                if (msg.startsWith('400 ILLEGAL ARGUMENT - Cannot apply') ||
                    msg.startsWith('400 ILLEGAL ARGUMENT - class_cast_exception:'))
                    throw new Error(`400 ILLEGAL ARGUMENT - failed to update due to type mismatch in item's field`); // at ES7.1
                if (msg.startsWith('400 ILLEGAL ARGUMENT'))
                    throw e; // at ES7.1
                throw E;
            }));
            // {"_index":"test-v3","_type":"_doc","_id":"aaa","_version":2,"result":"updated","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":8,"_primary_term":1}
            // {"_index":"test-v3","_type":"_doc","_id":"aaa","_version":2,"result":"noop","_shards":{"total":0,"successful":0,"failed":0}}
            (0, engine_1._log)(NS, `> update[${id}].res =`, engine_1.$U.json(Object.assign(Object.assign({}, res), { meta: undefined })));
            const _id = res.body._id;
            const _version = res.body._version;
            const res2 = Object.assign(Object.assign({}, item), { _id, _version });
            return res2;
        });
    }
    /**
     * run search and get the raw response.
     * @param body - Elasticsearch Query DSL that defines the search request (e.g., size, query, filters).
     * @param searchType - type of search (e.g., 'query_then_fetch', 'dfs_query_then_fetch').
     */
    searchRaw(body, searchType) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!body)
                throw new Error('@body (SearchBody) is required');
            const { indexName, docType } = this.options;
            (0, engine_1._log)(NS, `- search(${indexName}, ${searchType || ''})....`);
            (0, engine_1._log)(NS, `> body =`, engine_1.$U.json(body));
            const tmp = docType ? docType : '';
            const type = docType ? `${docType}` : undefined;
            const params = { index: indexName, body, searchType };
            // check version to include 'type' in params
            if (this.isOldES6) {
                params.type = type;
            }
            (0, engine_1._log)(NS, `> params[${tmp}] =`, engine_1.$U.json(Object.assign(Object.assign({}, params), { body: undefined })));
            // const { client } = instance(endpoint);
            const client = this.client;
            const $res = yield client.search(params).catch(exports.$ERROR.handler('search', e => {
                (0, engine_1._err)(NS, `> search[${indexName}].err =`, e);
                throw e;
            }));
            // {"took":6,"timed_out":false,"_shards":{"total":4,"successful":4,"skipped":0,"failed":0},"hits":{"total":1,"max_score":0.2876821,"hits":[{"_index":"test-v3","_type":"_doc","_id":"aaa","_score":0.2876821,"_source":{"name":"AAA","@id":"aaa","a":-3,"b":-2}}]}}
            // _log(NS, `> search[${id}].res =`, $U.json({ ...res, meta: undefined }));
            // _log(NS, `> search[${tmp}].took =`, $res.took);
            // _log(NS, `> search[${tmp}].hits.total =`, $res.hits?.total);
            // _log(NS, `> search[${tmp}].hits.max_score =`, $res.hits?.max_score);
            // _log(NS, `> search[${tmp}].hits.hits[0] =`, $res.hits && $U.json($res.hits.hits[0]));
            //* return raw results.
            return $res === null || $res === void 0 ? void 0 : $res.body;
        });
    }
    /**
     * run search, and get the formatmted response.
     * @param body - Elasticsearch Query DSL that defines the search request (e.g., size, query, filters).
     * @param searchType - type of search (e.g., 'query_then_fetch', 'dfs_query_then_fetch').
     *
     */
    search(body, searchType) {
        var _a, _b, _c;
        return __awaiter(this, void 0, void 0, function* () {
            const size = engine_1.$U.N(body.size, 0);
            const response = yield this.searchRaw(body, searchType);
            // return w/ transformed id
            const hits = response.hits;
            if (typeof hits !== 'object')
                throw new Error(`.hits (object) is required - hits:${engine_1.$U.json(hits)}`);
            //NOTE - ES6.8 w/ OS1.1
            return {
                total: typeof ((_a = hits.total) === null || _a === void 0 ? void 0 : _a.value) === 'number' ? (_b = hits.total) === null || _b === void 0 ? void 0 : _b.value : hits.total,
                list: hits.hits.map((hit) => (Object.assign(Object.assign({}, hit._source), { _id: hit._id, _score: hit._score }))),
                last: hits.hits.length === size && size > 0 ? (_c = hits.hits[size - 1]) === null || _c === void 0 ? void 0 : _c.sort : undefined,
                aggregations: response.aggregations,
            };
        });
    }
    /**
     * search all until limit (-1 means no-limit)
     * @param body - Elasticsearch Query DSL that defines the search request (e.g., size, query, filters).
     * @param params - parameters including search type, limit, and retry options.
     */
    searchAll(body, params) {
        var e_1, _a;
        return __awaiter(this, void 0, void 0, function* () {
            const list = [];
            try {
                for (var _b = __asyncValues(this.generateSearchResult(body, params)), _c; _c = yield _b.next(), !_c.done;) {
                    const chunk = _c.value;
                    chunk.forEach((N) => list.push(N));
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
                }
                finally { if (e_1) throw e_1.error; }
            }
            return list;
        });
    }
    /**
     * create async generator that yields items queried until last
     *
     * @param body - Elasticsearch Query DSL that defines the search request (e.g., size, query, filters).
     * @param params - parameters including search type, limit, and retry options.
     */
    generateSearchResult(body, params) {
        var _a, _b, _c, _d, _e, _f, _g;
        return __asyncGenerator(this, arguments, function* generateSearchResult_1() {
            const doRetry = (_b = (_a = params === null || params === void 0 ? void 0 : params.retryOptions) === null || _a === void 0 ? void 0 : _a.do) !== null && _b !== void 0 ? _b : false;
            const t = (_d = (_c = params === null || params === void 0 ? void 0 : params.retryOptions) === null || _c === void 0 ? void 0 : _c.t) !== null && _d !== void 0 ? _d : 5000;
            const maxRetries = (_f = (_e = params === null || params === void 0 ? void 0 : params.retryOptions) === null || _e === void 0 ? void 0 : _e.maxRetries) !== null && _f !== void 0 ? _f : 3;
            let limit = (_g = params === null || params === void 0 ? void 0 : params.limit) !== null && _g !== void 0 ? _g : -1;
            let retryCount = 0;
            if (!body.sort)
                body.sort = '_doc';
            do {
                try {
                    const { list, last } = yield __await(this.search(body, params === null || params === void 0 ? void 0 : params.searchType));
                    body.search_after = last;
                    if (list.length === 0 && !body.search_after) {
                        break;
                    }
                    yield yield __await(list);
                }
                catch (e) {
                    const msg = (0, test_helper_1.GETERR)(e);
                    if (doRetry && msg.startsWith('429 UNKNOWN') && retryCount < maxRetries) {
                        retryCount++;
                        yield __await((0, test_helper_1.waited)(t));
                        continue;
                    }
                    else {
                        throw e;
                    }
                }
            } while (body.search_after && (limit === -1 || --limit > 0));
        });
    }
    /**
     * prepare default setting
     * - migrated from engine-v2.
     *
     * @param docType       document type name
     * @param idName        id-name
     * @param shards        number of shards (default 4)
     * @param replicas      number of replicas (default 1)
     * @param timeSeries    flag of TIMESERIES (default false)
     */
    static prepareSettings(params) {
        const docType = params.docType === undefined ? '_doc' : params.docType;
        const idName = params.idName === undefined ? '$id' : params.idName;
        const version = engine_1.$U.F(params.version === undefined ? '6.8' : params.version);
        const shards = params.shards === undefined ? 4 : params.shards;
        const replicas = params.replicas === undefined ? 1 : params.replicas;
        const timeSeries = params.timeSeries === undefined ? false : params.timeSeries;
        //* core config.
        const CONF_ES_DOCTYPE = docType;
        const CONF_ID_NAME = idName;
        const CONF_ES_TIMESERIES = !!timeSeries;
        const ES_MAPPINGS = {
            // NOTE: the order of dynamic templates are important.
            dynamic_templates: [
                // 1. Search-as-You-Type (autocomplete search) - apply to '_decomposed.*' fields
                {
                    autocomplete: {
                        path_match: `${Elastic6Service.DECOMPOSED_FIELD}.*`,
                        mapping: {
                            type: 'text',
                            analyzer: 'autocomplete_case_insensitive',
                            search_analyzer: 'standard',
                        },
                    },
                },
                // 2. Search-as-You-Type (Korean to Alphabet sequence in QWERTY/2벌식 keyboard) - apply to '_qwerty.*' fields
                {
                    autocomplete_qwerty: {
                        path_match: `${Elastic6Service.QWERTY_FIELD}.*`,
                        mapping: {
                            type: 'text',
                            analyzer: 'autocomplete_case_sensitive',
                            search_analyzer: 'whitespace',
                        },
                    },
                },
                // 3. string type ID field
                {
                    string_id: {
                        match_mapping_type: 'string',
                        match: CONF_ID_NAME,
                        mapping: {
                            type: 'keyword',
                            ignore_above: 256,
                        },
                    },
                },
                // 4. any other string fields - use Hangul analyzer and create 'keyword' sub-field
                {
                    strings: {
                        match_mapping_type: 'string',
                        mapping: {
                            type: 'text',
                            analyzer: 'hangul',
                            search_analyzer: 'hangul',
                            fields: {
                                // keyword sub-field
                                // 문자열 타입에 대한 템플릿을 지정하지 않으면 기본으로 ES가 '.keyword' 서브필드를 생성하나
                                // 문자열 타입 템플릿 재정의 시 기본으로 생성되지 않으므로 명시적으로 선언함.
                                keyword: {
                                    type: 'keyword',
                                    ignore_above: 256,
                                },
                            },
                        },
                    },
                },
            ],
            properties: {
                '@version': {
                    type: 'keyword',
                    index: false,
                },
                created_at: {
                    type: 'date',
                    format: 'strict_date_optional_time||epoch_millis',
                },
                updated_at: {
                    type: 'date',
                    format: 'strict_date_optional_time||epoch_millis',
                },
                deleted_at: {
                    type: 'date',
                    format: 'strict_date_optional_time||epoch_millis',
                },
            },
        };
        //* default settings.
        const ES_SETTINGS = {
            settings: {
                number_of_shards: shards,
                number_of_replicas: replicas,
                analysis: {
                    tokenizer: {
                        hangul: {
                            type: 'seunjeon_tokenizer',
                            decompound: true,
                            deinflect: true,
                            index_eojeol: true,
                            pos_tagging: false, // 품사 태깅
                        },
                        edge_30grams: {
                            type: 'edge_ngram',
                            min_gram: 1,
                            max_gram: 30,
                            token_chars: ['letter', 'digit', 'punctuation', 'symbol'],
                        },
                    },
                    analyzer: {
                        hangul: {
                            type: 'custom',
                            tokenizer: 'hangul',
                            filter: ['lowercase'],
                        },
                        autocomplete_case_insensitive: {
                            type: 'custom',
                            tokenizer: 'edge_30grams',
                            filter: ['lowercase'],
                        },
                        autocomplete_case_sensitive: {
                            type: 'custom',
                            tokenizer: 'edge_30grams',
                            filter: version < 7 && version >= 6 ? ['standard'] : [], //* error - The [standard] token filter has been removed.
                        },
                    },
                },
            },
            //* since 7.x. no mapping for types.
            mappings: version < 7 && version >= 6 ? { [CONF_ES_DOCTYPE]: ES_MAPPINGS } : ES_MAPPINGS,
        };
        //* timeseries 데이터로, 기본 timestamp 값을 넣어준다. (주의! save시 current-time 값 자동 저장)
        if (!!CONF_ES_TIMESERIES) {
            ES_SETTINGS.settings.refresh_interval = '5s';
            if (version < 7 && version >= 6) {
                ES_SETTINGS.mappings[CONF_ES_DOCTYPE].properties['@timestamp'] = { type: 'date', doc_values: true };
                ES_SETTINGS.mappings[CONF_ES_DOCTYPE].properties['ip'] = { type: 'ip' };
                //* clear mappings.
                const CLEANS = '@version,created_at,updated_at,deleted_at'.split(',');
                CLEANS.map(key => delete ES_SETTINGS.mappings[CONF_ES_DOCTYPE].properties[key]);
            }
            else {
                ES_SETTINGS.mappings.properties['@timestamp'] = { type: 'date', doc_values: true };
                ES_SETTINGS.mappings.properties['ip'] = { type: 'ip' };
                //* clear mappings.
                const CLEANS = '@version,created_at,updated_at,deleted_at'.split(',');
                CLEANS.map(key => delete ES_SETTINGS.properties[key]);
            }
        }
        //* returns settings.
        return ES_SETTINGS;
    }
    /**
     * generate autocomplete fields into the item body to be indexed
     * @param body  item body to be saved into ES6 index
     * @private
     */
    popullateAutocompleteFields(body) {
        const { autocompleteFields } = this.options;
        const isAutoComplete = autocompleteFields && Array.isArray(autocompleteFields) && autocompleteFields.length > 0;
        if (!isAutoComplete)
            return body;
        return autocompleteFields.reduce((N, field) => {
            const value = body[field];
            if (typeof value == 'string' || value) {
                // 한글의 경우 자모 분해 형태와 영자판 변형 형태를 제공하고, 영문의 경우 원본 텍스트만 제공한다.
                // 다만 사용자가 공백/하이픈을 생략하고 입력하는 경우에 대응하기 위해 공백/하이픈을 제거한 형태를 공통으로 제공한다.
                if (hangul_service_1.default.isHangul(value, true)) {
                    // 자모 분해 (e.g. '레몬' -> 'ㄹㅔㅁㅗㄴ')
                    const decomposed = hangul_service_1.default.asJamoSequence(value);
                    const recomposed = decomposed.replace(/[ -]/g, '');
                    N[Elastic6Service.DECOMPOSED_FIELD][field] = [decomposed, recomposed];
                    // 영자판 (e.g. '레몬' -> 'fpahs')
                    N[Elastic6Service.QWERTY_FIELD][field] = hangul_service_1.default.asAlphabetKeyStokes(value);
                }
                else {
                    const recomposed = value.replace(/[ -]/g, '');
                    N[Elastic6Service.DECOMPOSED_FIELD][field] = [value, recomposed];
                }
            }
            return N;
        }, Object.assign(Object.assign({}, body), { [Elastic6Service.DECOMPOSED_FIELD]: {}, [Elastic6Service.QWERTY_FIELD]: {} }));
    }
}
exports.Elastic6Service = Elastic6Service;
// internal field name to store analyzed strings for autocomplete search
Elastic6Service.DECOMPOSED_FIELD = '_decomposed';
Elastic6Service.QWERTY_FIELD = '_qwerty';
/**
 * error samples
 */
exports.$ERROR = {
    asJson: (e) => {
        const _pack = (o) => JSON.parse(JSON.stringify(o, Object.getOwnPropertyNames(o)));
        if (e instanceof Error) {
            const err = e;
            const meta = err.meta && typeof err.meta === 'object' ? err.meta : undefined;
            const $err = _pack(err);
            return Object.assign(Object.assign({}, $err), { meta });
        }
        return e;
    },
    throwAsJson: (e) => {
        throw exports.$ERROR.asJson(e);
    },
    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;
        }
    },
    asError: (e) => {
        const E = exports.$ERROR.asJson(e);
        const status = `${E.statusCode || ''}`;
        const message = `${E.message || E.msg || ''}`;
        const reason = ((E) => {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
            //* from ES7.1
            if (E.meta && typeof E.meta == 'object') {
                const type = _S(E === null || E === void 0 ? void 0 : E.message).toUpperCase().split('_').slice(0, -1).join(' ');
                const status = engine_1.$U.N((_a = E.meta) === null || _a === void 0 ? void 0 : _a.statusCode, type.includes('NOT FOUND') ? 404 : 400);
                const $res = exports.$ERROR.parseMeta(E.meta);
                //* find the reason.
                const reason = ((_c = (_b = $res.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.reason) === 'failed to execute script'
                    ? (_g = (_f = (_e = (_d = $res.body) === null || _d === void 0 ? void 0 : _d.error) === null || _e === void 0 ? void 0 : _e.caused_by) === null || _f === void 0 ? void 0 : _f.caused_by) === null || _g === void 0 ? void 0 : _g.reason
                    : (_j = (_h = $res.body) === null || _h === void 0 ? void 0 : _h.error) === null || _j === void 0 ? void 0 : _j.reason;
                const result = { status, type: type || (status === 404 ? 'NOT FOUND' : 'UNKNOWN') };
                if (typeof reason !== 'undefined') {
                    result.reason = reason;
                }
                return result;
            }
            //* from ES6.2
            if (!E.response)
                return null;
            const $res = exports.$ERROR.parseMeta(E.response);
            //* find the root-cause.
            const pic1 = (N, i = 0) => (N && Array.isArray(N) ? N[i] : N);
            const cause = pic1((_k = $res === null || $res === void 0 ? void 0 : $res.error) === null || _k === void 0 ? void 0 : _k.root_cause);
            const status = engine_1.$U.N(((_l = $res.error) === null || _l === void 0 ? void 0 : _l.status) || $res.status);
            const reason = _S((_m = $res.error) === null || _m === void 0 ? void 0 : _m.reason, $res.found === false || $res.result === 'not_found' ? 'NOT FOUND' : '');
            const type = _S(cause === null || cause === void 0 ? void 0 : cause.type).toUpperCase().split('_').slice(0, -1).join(' ');
            return { status, reason, cause, type: type || reason };
        })(E);
        //* FINAL. convert to error-object.
        return {
            status: engine_1.$U.N(status, (reason === null || reason === void 0 ? void 0 : reason.status) || 0),
            message: message || (reason === null || reason === void 0 ? void 0 : reason.reason),
            reason,
        };
    },
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    handler: (name, cb) => (e) => {
        var _a, _b, _c, _d, _e;
        const E = exports.$ERROR.asError(e);
        //* unknown error found..
        if (!(E === null || E === void 0 ? void 0 : E.status)) {
            (0, engine_1._err)(NS, `! err[${name}]@handler =`, e instanceof Error, engine_1.$U.json(e));
            throw e;
        }
        const $e = new Error(`${E.status} ${(_b = (_a = E.reason) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : ''} - ${(_e = (_d = (_c = E.reason) === null || _c === void 0 ? void 0 : _c.reason) !== null && _d !== void 0 ? _d : E.message) !== null && _e !== void 0 ? _e : ''}`);
        if (cb)
            return cb($e, E);
        throw $e;
    },
};
/** ****************************************************************************************************************
 *  Dummy Elastic6 Service
 ** ****************************************************************************************************************/
/**
 * class: `DummyElastic6Service`
 * - service in-memory dummy data
 */
class DummyElastic6Service extends Elastic6Service {
    constructor(dataFile, options) {
        super(options);
        this.buffer = {};
        /**
         * say hello()
         */
        this.hello = () => `dummy-elastic6-service:${this.options.indexName}`;
        (0, engine_1._log)(NS, `DummyElastic6Service(${dataFile || ''})...`);
        if (!dataFile)
            throw new Error('@dataFile(string) is required!');
        const dummy = (0, tools_1.loadDataYml)(dataFile);
        this.load(dummy.data);
    }
    load(data) {
        const { idName } = this.options;
        if (!data || !Array.isArray(data))
            throw new Error('@data should be array!');
        data.map(item => {
            const id = `${item[idName] || ''}`;
            this.buffer[id] = item;
        });
    }
    readItem(id) {
        return __awaiter(this, void 0, void 0, function* () {
            const item = this.buffer[id];
            if (item === undefined)
                throw new Error(`404 NOT FOUND - id:${id}`);
            return item;
        });
    }
    saveItem(id, item) {
        return __awaiter(this, void 0, void 0, function* () {
            const { idName } = this.options;
            this.buffer[id] = Object.assign({ id }, item);
            return Object.assign(Object.assign({ [idName]: id }, item), { _version: 1 });
        });
    }
    deleteItem(id, sort) {
        return __awaiter(this, void 0, void 0, function* () {
            const org = this.buffer[id];
            delete this.buffer[id];
            return Object.assign({}, org);
        });
    }
    updateItem(id, updates, increments) {
        return __awaiter(this, void 0, void 0, function* () {
            const org = yield this.readItem(id);
            const item = Object.assign(Object.assign(Object.assign({}, org), updates), { _version: Number(org._version || 0) + 1 });
            if (increments) {
                Object.entries(increments).forEach(([key, value]) => {
                    if (Array.isArray(value)) {
                        // case of arrary increment
                        if (Array.isArray(item[key])) {
                            item[key] = [...item[key], ...value];
                        }
                        else {
                            item[key] = value;
                        }
                    }
                    else {
                        if (typeof item[key] === 'number') {
                            item[key] = item[key] + value;
                        }
                        else {
                            item[key] = value;
                        }
                    }
                });
            }
            this.buffer[id] = item;
            return Object.assign({ id: id, _version: Number(org._version || 0) + 1 }, updates);
        });
    }
}
exports.DummyElastic6Service = DummyElastic6Service;
//# sourceMappingURL=elastic6-service.js.map