lemon-core
Version:
Lemon Serverless Micro-Service Platform
377 lines • 14.7 kB
JavaScript
"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-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