UNPKG

lemon-core

Version:
377 lines 14.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DummyStorageService = exports.DynamoStorageService = void 0; /** * `storage-service.js` * - common service for `storage` * * @author Steve Jung <steve@lemoncloud.io> * @date 2019-09-26 initial version * @date 2019-10-01 moved from ticket-data-service to storage-service. * @date 2019-12-01 migrated to storage-service. * * @copyright (C) 2019 LemonCloud Co Ltd. - All Rights Reserved. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const engine_1 = require("../../engine/"); const NS = engine_1.$U.NS('STRS', 'green'); // NAMESPACE TO BE PRINTED. /** **************************************************************************************************************** * Data Storage Service ** ****************************************************************************************************************/ const dynamo_1 = require("../dynamo/"); const tools_1 = require("../../tools/"); const clearDuplicated = (arr) => arr.sort().reduce((L, val) => { if (L.indexOf(val) < 0) L.push(val); return L; }, []); /** * class: `DynamoStorageService` * - service via DynamoDB with id + json data. */ class DynamoStorageService { constructor(table, fields, idName = 'id', idType = 'string') { /** * say hello() * @param name (optional) given name */ this.hello = () => `dynamo-storage-service:${this._table}/${this._idName}/${this._fields.length}`; /** * (extended) get copy of fields. */ this.fields = () => [...this._fields]; if (!table) throw new Error(`@table (table-name) is required!`); this._table = table; this._idName = idName; this._fields = clearDuplicated(['id', 'type', 'stereo', 'meta', idName].concat(fields)); this.$dynamo = new dynamo_1.DynamoService({ tableName: this._table, idName, idType }); } /** * Read whole model via database. * * @param id id */ read(id) { return __awaiter(this, void 0, void 0, function* () { const data = yield this.$dynamo.readItem(id); const fields = this._fields || []; const item = fields.reduce((N, key) => { const val = data[key]; if (val !== undefined) N[key] = val; return N; }, {}); return item; }); } /** * auto-create if not found. * * @param id * @param model */ readOrCreate(id, model) { return __awaiter(this, void 0, void 0, function* () { return this.read(id).catch((e) => { if (`${e.message}`.startsWith('404 NOT FOUND')) return this.update(id, model); throw e; }); }); } /** * simply save(or overwrite) all model * * @param id id * @param model whole object. */ save(id, model) { return __awaiter(this, void 0, void 0, function* () { const fields = this._fields || []; const data = fields.reduce((N, key) => { const val = model[key]; if (val !== undefined) N[key] = val; return N; }, {}); yield this.$dynamo.saveItem(id, data); // must be `{}` const item = Object.assign({ [this._idName]: id }, data); return item; }); } /** * update some attributes * * @param id id * @param model attributes to update * @param incrementals (optional) attributes to increment */ update(id, model, incrementals) { return __awaiter(this, void 0, void 0, function* () { const fields = this._fields || []; const $U = fields.reduce((N, key) => { const val = model[key]; if (val !== undefined) N[key] = val; return N; }, {}); /* eslint-disable prettier/prettier */ const $I = !incrementals ? null : Object.keys(incrementals).reduce((M, key) => { const val = incrementals[key]; if (typeof val !== 'number') throw new Error(`.${key} (${val}) should be number!`); M[key] = val; return M; }, {}); /* eslint-enable prettier/prettier */ const ret = yield this.$dynamo.updateItem(id, undefined, $U, $I); return ret; }); } /** * increment number attribute (or overwrite string field) * - if not exists, then just update property with base zero 0. * * @param id id * @param model attributes of number. * @param $update (optional) update-set. */ increment(id, model, $update) { return __awaiter(this, void 0, void 0, function* () { if (!model && !$update) throw new Error('@item is required!'); const $org = yield this.read(id).catch(e => { if (`${e.message || e}`.startsWith('404 NOT FOUND')) return { id }; throw e; }); const fields = this._fields || []; const $U = fields.reduce((N, key) => { const val = $update ? $update[key] : undefined; if (val !== undefined) N[key] = val; return N; }, {}); const $I = fields.reduce((N, key) => { const val = model[key]; if (val !== undefined) { const org = $org[key]; //* check type matched! if (org !== undefined && typeof org === 'number' && typeof val !== 'number') throw new Error(`.${key} (${val}) should be number!`); //* if not exists, update it. if (org === undefined && typeof val === 'number') N[key] = val; else if (typeof val !== 'number' && !Array.isArray(val)) $U[key] = val; else N[key] = val; } return N; }, {}); const ret = yield this.$dynamo.updateItem(id, undefined, $U, $I); return ret; }); } /** * delete set. * - if not exists, then just update property with base zero 0. * * @param id id */ delete(id) { return __awaiter(this, void 0, void 0, function* () { const $org = yield this.read(id); yield this.$dynamo.deleteItem(id); return $org; }); } } exports.DynamoStorageService = DynamoStorageService; /** **************************************************************************************************************** * Dummy Data Service ** ****************************************************************************************************************/ /** * class: `DummyStorageService` * - service in-memory dummy data * * **NOTE** * - this dummy service should be replaceable with real service `DynamoStorageService` */ class DummyStorageService { constructor(dataFile, name = 'memory', idName) { this.buffer = {}; /** * say hello() * @param name (optional) given name */ this.hello = () => `dummy-storage-service:${this.name}/${this.idName}`; this.$locks = {}; //* only for lock. (0, engine_1._log)(NS, `DummyStorageService(${dataFile || ''})...`); if (!dataFile) throw new Error('@dataFile(string) is required!'); this.name = `${name || ''}`; this.idName = `${idName || 'id'}`; // const loadDataYml = require('../express').loadDataYml; const dummy = (0, tools_1.loadDataYml)(dataFile); this.load(dummy.data); } load(data) { if (!data || !Array.isArray(data)) throw new Error('@data should be array!'); data.map(item => { const id = item.id || ''; this.buffer[id] = item; }); } read(id) { return __awaiter(this, void 0, void 0, function* () { if (!id.trim()) throw new Error('@id (string) is required!'); const item = this.buffer[id]; if (!item) throw new Error(`404 NOT FOUND - ${this.idName}:${id}`); return Object.assign(Object.assign({}, item), { [this.idName]: id }); }); } readSafe(id) { return __awaiter(this, void 0, void 0, function* () { return this.read(id).catch(e => { if (`${e.message || e}`.startsWith('404 NOT FOUND')) { const $org = { [this.idName]: id }; return $org; } throw e; }); }); } readOrCreate(id, model) { return __awaiter(this, void 0, void 0, function* () { return this.read(id).catch((e) => { if (`${e.message}`.startsWith('404 NOT FOUND')) return this.update(id, model); throw e; }); }); } save(id, item) { return __awaiter(this, void 0, void 0, function* () { if (!id) throw new Error('@id is required!'); if (!item) throw new Error('@item is required!'); if (item && typeof item.lock == 'number') this.$locks[id] = item.lock; this.buffer[id] = item; return Object.assign({ [this.idName]: id }, item); }); } update(id, item, $inc) { return __awaiter(this, void 0, void 0, function* () { if (!id) throw new Error('@id is required!'); if (!item) throw new Error('@item is required!'); //* atomic operation for `.lock` const lock = (() => { let lock = 0; if (item && typeof item.lock == 'number') this.$locks[id] = lock = item.lock; if ($inc && typeof $inc.lock == 'number') this.$locks[id] = lock = $inc.lock + engine_1.$U.N(this.$locks[id], 0); return lock; })(); const $org = yield this.readSafe(id); const $new = Object.assign($org, item); /* eslint-disable prettier/prettier */ const incremented = !$inc ? null : Object.keys($inc).reduce((M, key) => { const val = $inc[key]; if (typeof val !== 'number') throw new Error(`.${key} (${val}) should be number!`); if (key == 'lock') { M[key] = lock; } else { M[key] = engine_1.$U.N($new[key], 0) + val; } return M; }, {}); if (incremented) Object.assign($new, incremented); /* eslint-enable prettier/prettier */ yield this.save(id, $new); const $set = Object.assign(Object.assign({}, item), incremented); if (typeof $set.lock == 'number') $set.lock = lock; return Object.assign({ [this.idName]: id }, $set); }); } increment(id, $inc, $upt) { return __awaiter(this, void 0, void 0, function* () { if (!id) throw new Error('@id is required!'); if (!$inc && !$upt) throw new Error('@item is required!'); //* atomic operation for `.lock` const lock = (() => { let lock = 0; if ($upt && typeof $upt.lock == 'number') this.$locks[id] = lock = $upt.lock; if ($inc && typeof $inc.lock == 'number') this.$locks[id] = lock = $inc.lock + engine_1.$U.N(this.$locks[id], 0); return lock; })(); const $org = yield this.readSafe(id); const $set = Object.keys($inc) .concat(Object.keys($upt || {})) .reduce((N, key) => { const val = $inc ? $inc[key] : undefined; const upt = $upt ? $upt[key] : undefined; const org = $org[key]; if (upt !== undefined) { N[key] = upt; } else if (val !== undefined) { if (org !== undefined && typeof org === 'number' && typeof val !== 'number') throw new Error(`.${key} (${val}) should be number!`); if (typeof val !== 'number') { N[key] = val; } else if (key == 'lock') { N[key] = lock; $org[key] = lock; } else { N[key] = (org === undefined ? 0 : org) + val; $org[key] = (org === undefined ? 0 : org) + val; } } return N; }, {}); if (typeof $set.lock == 'number') $set.lock = lock; yield this.save(id, Object.assign($org, $set)); return Object.assign({ [this.idName]: id }, $set); }); } delete(id) { return __awaiter(this, void 0, void 0, function* () { const $org = yield this.read(id); delete this.buffer[id]; delete this.$locks[id]; return $org; }); } } exports.DummyStorageService = DummyStorageService; //# sourceMappingURL=storage-service.js.map