UNPKG

@bxjs/base

Version:

bxjs base framework & api

605 lines 76.2 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); /** * 研究阿里云的OTS表格设计将session、cache、user三种缓存信息算法构建在此之上。 */ var TableStore = require('tablestore'); var Long = TableStore.Long; var _ = require('lodash'); var NoSqlInterface = /** @class */ (function () { function NoSqlInterface(table, config) { if (config === void 0) { config = 'default'; } } return NoSqlInterface; }()); // export async function test() { // const ots = new NoSqlAliyunTablestore('session_test3') // // let out = await ots.create(60) // // FIXME 首次创建表需要有一定的系统延迟时间,需要放到install过程中进行维护。 // // let out = await ots.insert('x', {a: 'aaa', b: {value: 'bbbb', timestamp: Date.now()}}) // // let out = await ots.update('x', {a: 'aaa2', b: 'bbb2'}) // // let out = await ots.update('x', {a: null, b: 1529666053100}) // // let out = await ots.delete('x') // // let out // // out = await ots.insert('x', {a: 'aaa', b: {value: 'bbbb', timestamp: Date.now()}}) // // out = await ots.query('x') // 取全部的字段 // // out = await ots.query('x', ['a']) // 取部分字段 // // out = await ots.query('y') // 取不存在的主键 // // // out = await ots.create(24*60*60,2) // // out = await ots.insert('z', {a: 'aaa'+Date(), b: {value: 'bbbb'+Date(), timestamp: Date.now()}}) // // out = await ots.update('z', {a: 'aaa'+Date(), b: 'bbbb'+Date()}) // // out = await ots.query('z', null, 2) // out = await ots.query('z', null, 1) // // 用最佳方案去实现字段 // return out // } // 对于阿里云tablestore的单表功能简单封装处理(一个主键并作为分区唯一区分一个记录,仅用于缓存功能实现。 // 封装特点:每个记录一条主键并作为分区,每个记录有无数个kv值可供配置,每个kv值都有一个时间戳超时时间在表上单独配置。) var NoSqlAliyunTablestore = /** @class */ (function (_super) { __extends(NoSqlAliyunTablestore, _super); function NoSqlAliyunTablestore(table, config) { if (config === void 0) { config = 'default'; } var _this = _super.call(this, table, config) || this; var instances = xconfig('plugins.nosql'); xassert(instances && _.isPlainObject(instances) && config in instances, ERR$CONFIG, { instances: instances }); _this._table = table; _this._schema = { KEYS: { id: 'string' // 表的唯一记录主键值同时也是分区 } }; _this._client = new TableStore.Client({ accessKeyId: instances[config].OTS_ACCESS_KEY_ID, secretAccessKey: instances[config].OTS_SECRETE_ACCESS_KEY, endpoint: instances[config].OTS_ENDPOINT, instancename: instances[config].OTS_INSTANCENAME }); return _this; } Object.defineProperty(NoSqlAliyunTablestore.prototype, "table", { set: function (value) { this._table = value; }, enumerable: true, configurable: true }); // 约定所有表都需要有自增主键id作为内部唯一标识码,其余三个主键必须全部为字符串。(后端需要扩展int和date基本数据类型以及操作符重载实现优化开发) // 属性字段可以根据业务演变任意的扩展增加由业务代码对于老数据不存在新增字段值的情况做兼容处理 NoSqlAliyunTablestore.prototype.insert = function (id, kvt) { return __awaiter(this, void 0, void 0, function () { var _a, _b, __this__, params, k; return __generator(this, function (_c) { xassert(Object.keys(kvt).length <= 128); // 规避跨行限制总计属性128个 __this__ = this; params = { tableName: this._table, // 插入的时候需要确保不存在对应的数据以防止出错 condition: new TableStore.Condition(TableStore.RowExistenceExpectation.EXPECT_NOT_EXIST, null), primaryKey: [], attributeColumns: [], // 按照下面数据格式进行schema定义的验证以及数据类型转换 // primaryKey: [{'gid': Long.fromNumber(20013)}, {'uid': Long.fromNumber(20013)}], // attributeColumns: [ // {'col1': '表格存储'}, // {'col2': '2', 'timestamp': currentTimeStamp}, // 允许修改时间戳乐观锁功能实现暂不支持 // {'col3': 3.1}, // {'col4': -0.32}, // {'col5': Long.fromNumber(123456789)} // ], // primaryKey: [ // {'short_id': 'pk1'}, // {[AUTO_KEY_NAME]: TableStore.PK_AUTO_INCR} // ], // attributeColumns: [ // {'appcode': 'app1'} // ], returnContent: { returnType: TableStore.ReturnType.Primarykey } }; // 拼接主键以及属性字段值 params.primaryKey = [{ 'id': id }]; for (k in kvt) { xassert(k != 'id' && k != 'timestamp'); // 两个预留内部标识符不可作为属性名 if (_.isString(kvt[k])) { params.attributeColumns.push((_a = {}, _a[k] = kvt[k], _a)); } else { params.attributeColumns.push((_b = {}, _b[k] = kvt[k]['value'], _b.timestamp = kvt[k]['timestamp'], _b)); } } return [2 /*return*/, new Promise(function (resolve, reject) { try { __this__._client.putRow(params, function (err, out) { if (err) { xthrow(new Error(err), reject, { params: params, out: out }); return; } // 正常返回的数据格式 //{"consumed":{"capacity_unit":{"read":0,"write":1}},"row":{ // "primaryKey":[{"name":"short_id","value":"abcd"},{"name":"id","value":1520765502347000}], // "attributes":[]}, // "RequestId":"00056720-cf8d-d4a8-8ae8-970a17894ce6"} resolve(out); }); } catch (err) { xthrow(err, reject); } })]; }); }); }; NoSqlAliyunTablestore.prototype.update = function (id, kvt) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._update_or_replace(id, kvt, false)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; NoSqlAliyunTablestore.prototype.replace = function (id, kvt) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._update_or_replace(id, kvt, true)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; // 条件更新必须要填写主键记录值(仅仅允许更新属性字段而主键字段是不允许更新的, // 整列更新和删除列属于运维操作禁止应用中使用需要单独接口以及权限认证) NoSqlAliyunTablestore.prototype._update_or_replace = function (id, kvt, isIgnoreRowNonExist) { if (isIgnoreRowNonExist === void 0) { isIgnoreRowNonExist = false; } return __awaiter(this, void 0, void 0, function () { var _a, _b, __this__, params, PUT, DELETE, DELETE_ALL, k; return __generator(this, function (_c) { xassert(Object.keys(kvt).length > 0 && Object.keys(kvt).length <= 128); // 规避跨行限制总计属性128个 __this__ = this; console.log('整行更新table:', this._table); console.log('更新的字段:', JSON.stringify(kvt)); params = { tableName: this._table, condition: new TableStore.Condition(isIgnoreRowNonExist ? TableStore.RowExistenceExpectation.IGNORE : TableStore.RowExistenceExpectation.EXPECT_EXIST, null), primaryKey: [{ id: id }], // updateOfAttributeColumns: [{'PUT': [{'col1': 'test6'}]}] updateOfAttributeColumns: [] // updateOfAttributeColumns: [ // { 'PUT': [{ 'col4': Long.fromNumber(4) }, { 'col5': '5' }, { 'col6': Long.fromNumber(6) }] }, // { 'DELETE': [{ 'col1': Long.fromNumber(1496826473186) }] }, // 删除指定时间戳版本数据 // { 'DELETE_ALL': ['col2'] } // 删除所有版本的字段数据 // ] }; PUT = []; DELETE = []; DELETE_ALL = []; for (k in kvt) { // 如果变量值为null类型则表示删除对应的字段值,如果为整数表示删除指定时间戳版本,否则表示添加或更新对应字段值。 if (!kvt[k]) { DELETE_ALL.push(k); } else if (_.isInteger(kvt[k])) { DELETE.push((_a = {}, _a[k] = Long.fromNumber(kvt[k]), _a)); } else if (_.isString(kvt[k])) { PUT.push((_b = {}, _b[k] = kvt[k], _b)); } else { xassert(false, ERR$PARAM, { id: id, kvt: kvt }); } } if (PUT.length > 0) params.updateOfAttributeColumns.push({ PUT: PUT }); if (DELETE.length > 0) params.updateOfAttributeColumns.push({ DELETE: DELETE }); if (DELETE_ALL.length > 0) params.updateOfAttributeColumns.push({ DELETE_ALL: DELETE_ALL }); return [2 /*return*/, new Promise(function (resolve, reject) { try { console.log('更新数据记录:', JSON.stringify(params)); __this__._client.updateRow(params, function (err, data) { console.log('更新的结果或返回值:', JSON.stringify(err)); console.log('更新的结果或返回值1:', JSON.stringify(data)); if (err) { xthrow(new Error(err), reject, { params: params, data: data }); return; } resolve(); }); } catch (err) { xthrow(err, reject); } })]; }); }); }; // 以主键作为条件删除记录值 NoSqlAliyunTablestore.prototype.delete = function (id) { return __awaiter(this, void 0, void 0, function () { var __this__, params; return __generator(this, function (_a) { __this__ = this; console.log('删除指定的表:', this._table); params = { tableName: this._table, condition: new TableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, null), // primaryKey: [{ 'gid': Long.fromNumber(8) }, { 'uid': Long.fromNumber(80) }] primaryKey: [{ id: id }] }; return [2 /*return*/, new Promise(function (resolve, reject) { try { __this__._client.deleteRow(params, function (err, data) { if (err) { xthrow(new Error(err), reject, { params: params, data: data }); return; } resolve(); }); } catch (err) { xthrow(err, reject); } })]; }); }); }; // 范围查询需要数据自动同步到opensearch进行索引同步后进行各种复杂的查询操作实现免运维系统的实现 // 单表逻辑条件的简单and与equal的查询,返回满足条件的第一条记录 (合并为一个查询兼容mongodb的查询扩展) NoSqlAliyunTablestore.prototype.query = function (id, keys, max_version) { if (keys === void 0) { keys = null; } if (max_version === void 0) { max_version = 1; } return __awaiter(this, void 0, void 0, function () { var __this__, params; return __generator(this, function (_a) { __this__ = this; params = { tableName: this._table, columnsToGet: keys, // columns_to_get 获取期望的列最多128个一次获取总数,应用上应该将KEY视为分组总数。 // 如何规避宽表的分页限制?? FIXME 先从应用上规避限制一个应用最多不超过128个属性,通过JSON进行扩展存储。 primaryKey: [{ id: id }], // primaryKey: [{'gid': Long.fromNumber(20013)}, {'uid': Long.fromNumber(20013)}], columnFilter: null, maxVersions: max_version, }; return [2 /*return*/, new Promise(function (resolve, reject) { __this__._client.getRow(params, function (err, data) { if (err) { xthrow(new Error(err), reject, { params: params, data: data }); return; } // 返回数据格式类型进行转换处理 // {"consumed":{"capacity_unit":{"read":1,"write":0}}, // "row":{"primaryKey":[{"name":"gid","value":20013},{"name":"uid","value":20013}], // "attributes":[{"columnName":"col1","columnValue":"表格存储","timestamp":1520734520286}, // {"columnName":"col2","columnValue":"2","timestamp":1520734520064}, // {"columnName":"col3","columnValue":3.1,"timestamp":1520734520286}, // {"columnName":"col4","columnValue":-0.32,"timestamp":1520734520286}, // {"columnName":"col5","columnValue":123456789,"timestamp":1520734520286}] // }, // "next_token":null,"RequestId":"00056719-e2ad-73b1-dbd8-970a19522f4b"} // 将数据结果进行转换处理合并为一个普通对象给应用使用 try { var out = {}; if (!data.row) { return resolve(null); } if (data.row.primaryKey) { for (var k in data.row.primaryKey) { out[data.row.primaryKey[k].name] = data.row.primaryKey[k].value; } } if (data.row.attributes) { for (var k in data.row.attributes) { out[data.row.attributes[k].columnName] = data.row.attributes[k].columnValue; } } // xlog(data) // TODO 当存在多个版本数据的时候解析不正确,应该是多个版本的属性值字段的组合才正确。 resolve(_.isEmpty(out) ? null : out); } catch (err) { xthrow(new Error(err), reject, { params: params, data: data }); return; } }); })]; }); }); }; // 销毁表 NoSqlAliyunTablestore.prototype.destroy = function () { return __awaiter(this, void 0, void 0, function () { var err_1, i, out, err_2; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, this._destroy()]; case 1: _a.sent(); return [3 /*break*/, 3]; case 2: err_1 = _a.sent(); return [2 /*return*/]; case 3: i = 0; _a.label = 4; case 4: if (!(i < 100)) return [3 /*break*/, 9]; _a.label = 5; case 5: _a.trys.push([5, 6, , 8]); out = this.describe(); xlog(out); return [3 /*break*/, 8]; case 6: err_2 = _a.sent(); return [4 /*yield*/, xsleep(100)]; case 7: _a.sent(); return [3 /*break*/, 9]; case 8: i++; return [3 /*break*/, 4]; case 9: return [2 /*return*/]; } }); }); }; NoSqlAliyunTablestore.prototype._destroy = function () { return __awaiter(this, void 0, void 0, function () { var __this__, params; return __generator(this, function (_a) { __this__ = this; params = { tableName: this._table }; return [2 /*return*/, new Promise(function (resolve, reject) { __this__._client.deleteTable(params, function (err, data) { if (err) { xthrow(new Error(err), reject, { params: params }); return; } resolve(); }); })]; }); }); }; // 创建表 NoSqlAliyunTablestore.prototype.create = function (timeout, max_versions) { if (timeout === void 0) { timeout = -1; } if (max_versions === void 0) { max_versions = 1; } return __awaiter(this, void 0, void 0, function () { var i, out, err_3; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._create(timeout, max_versions) // 10秒钟等待超时创建表正常完成 ]; case 1: _a.sent(); i = 0; _a.label = 2; case 2: if (!(i < 100)) return [3 /*break*/, 8]; _a.label = 3; case 3: _a.trys.push([3, 4, , 6]); out = this.describe(); xlog(out); return [3 /*break*/, 6]; case 4: err_3 = _a.sent(); return [4 /*yield*/, xsleep(100)]; case 5: _a.sent(); return [3 /*break*/, 7]; case 6: return [3 /*break*/, 8]; case 7: i++; return [3 /*break*/, 2]; case 8: return [2 /*return*/]; } }); }); }; NoSqlAliyunTablestore.prototype._create = function (timeout, max_versions) { if (timeout === void 0) { timeout = -1; } if (max_versions === void 0) { max_versions = 1; } return __awaiter(this, void 0, void 0, function () { var __this__, params, k, obj, out; return __generator(this, function (_a) { switch (_a.label) { case 0: // OTS最长超时时间为1天的兼容处理 if (timeout != -1 && timeout < 86400) { timeout = 86400; } __this__ = this; params = { tableMeta: { tableName: this._table, primaryKey: [], }, reservedThroughput: { capacityUnit: { read: 0, write: 0 } }, tableOptions: { timeToLive: timeout, maxVersions: max_versions, } }; // 自动转换schema定义为OTS的数据结构 for (k in this._schema.KEYS) { obj = { name: k, type: _.upperCase(this._schema.KEYS[k]) }; // if (k == AUTO_KEY_NAME) { // obj['option'] = 'AUTO_INCREMENT' // } params.tableMeta.primaryKey.push(obj); } return [4 /*yield*/, new Promise(function (resolve, reject) { __this__._client.createTable(params, function (err, data) { if (err) { xthrow(new Error(err), reject, { params: params, data: data }); return; } resolve(); }); }) // FIXME 表创建后有一定的延时时间才能生效,需要维持一定的等待时间确保正常执行完成。 // TODO 延时算法自动完成时间戳的处理。 ]; case 1: out = _a.sent(); // FIXME 表创建后有一定的延时时间才能生效,需要维持一定的等待时间确保正常执行完成。 // TODO 延时算法自动完成时间戳的处理。 return [2 /*return*/, out]; } }); }); }; // 查询当前表的描述信息 NoSqlAliyunTablestore.prototype.describe = function () { return __awaiter(this, void 0, void 0, function () { var __this__, params; return __generator(this, function (_a) { __this__ = this; params = { tableName: this._table }; return [2 /*return*/, new Promise(function (resolve, reject) { __this__._client.describeTable(params, function (err, data) { if (err) { xthrow(new Error(err), reject, { params: params }); return; } resolve(data); }); })]; }); }); }; // 表配置信息的更新处理(时间戳以及版本号) NoSqlAliyunTablestore.prototype.change = function (param) { return __awaiter(this, void 0, void 0, function () { var __this__, params; return __generator(this, function (_a) { __this__ = this; params = { tableName: this._table, tableOptions: { // 保存的最大版本数, 设置为1即代表每列上最多保存一个版本(保存最新的版本). maxVersions: param.maxVersions ? param.maxVersions : 1, // 数据的过期时间, 单位秒, -1代表永不过期. 假如设置过期时间为一年, 即为 365 * 24 * 3600 timeToLive: param.timeoutSeconds ? param.timeoutSeconds : -1, }, reservedThroughput: { capacityUnit: { // 为了提升并发度确保预留最小读写数量的配置避免服务共享可能产生的资源竞争不稳定问题 read: param.reservedThroughputRead ? param.reservedThroughputRead : 0, write: param.reservedThroughputWrite ? param.reservedThroughputWrite : 0, } }, }; return [2 /*return*/, new Promise(function (resolve, reject) { __this__._client.updateTable(params, function (err, data) { if (err) { xthrow(new Error(err), reject, { params: params }); return; } resolve(data); }); })]; }); }); }; // 重置表 NoSqlAliyunTablestore.prototype.reset = function () { return __awaiter(this, void 0, void 0, function () { var err_4; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, this.destroy()]; case 1: _a.sent(); return [3 /*break*/, 3]; case 2: err_4 = _a.sent(); return [3 /*break*/, 3]; case 3: return [4 /*yield*/, this.create()]; case 4: _a.sent(); return [2 /*return*/]; } }); }); }; return NoSqlAliyunTablestore; }(NoSqlInterface)); exports.NoSqlAliyunTablestore = NoSqlAliyunTablestore; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxpeXVuX290cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImFsaXl1bl9vdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTs7R0FFRztBQUNILElBQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQTtBQUN4QyxJQUFNLElBQUksR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFBO0FBQzVCLElBQU0sQ0FBQyxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtBQUUzQjtJQUNJLHdCQUFZLEtBQWEsRUFBRSxNQUFpQztRQUFqQyx1QkFBQSxFQUFBLGtCQUFpQztJQUM1RCxDQUFDO0lBaUNMLHFCQUFDO0FBQUQsQ0FBQyxBQW5DRCxJQW1DQztBQUVELGlDQUFpQztBQUNqQyw2REFBNkQ7QUFDN0Qsd0NBQXdDO0FBQ3hDLHFEQUFxRDtBQUNyRCxnR0FBZ0c7QUFDaEcsaUVBQWlFO0FBQ2pFLHNFQUFzRTtBQUN0RSx5Q0FBeUM7QUFDekMsRUFBRTtBQUNGLGNBQWM7QUFDZCw0RkFBNEY7QUFDNUYsK0NBQStDO0FBQy9DLHFEQUFxRDtBQUNyRCxnREFBZ0Q7QUFDaEQsRUFBRTtBQUNGLDRDQUE0QztBQUM1QywwR0FBMEc7QUFDMUcsMEVBQTBFO0FBQzFFLDZDQUE2QztBQUM3QywwQ0FBMEM7QUFDMUMsb0JBQW9CO0FBQ3BCLGlCQUFpQjtBQUNqQixJQUFJO0FBRUosMERBQTBEO0FBQzFELCtEQUErRDtBQUMvRDtJQUEyQyx5Q0FBYztJQVNyRCwrQkFBWSxLQUFhLEVBQUUsTUFBMEI7UUFBMUIsdUJBQUEsRUFBQSxrQkFBMEI7UUFBckQsWUFDSSxrQkFBTSxLQUFLLEVBQUUsTUFBTSxDQUFDLFNBaUJ2QjtRQWhCRyxJQUFNLFNBQVMsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUE7UUFDMUMsT0FBTyxDQUFDLFNBQVMsSUFBSSxDQUFDLENB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QUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxNQUFNLEVBQUUsVUFBVSxHQUFHLEVBQUUsSUFBSTs0QkFDcEQsSUFBSSxHQUFHLEVBQUU7Z0NBQ0wsTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFLE1BQU0sRUFBRSxFQUFDLE1BQU0sUUFBQSxFQUFDLENBQUMsQ0FBQTtnQ0FDeEMsT0FBTTs2QkFDVDs0QkFDRCxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7d0JBQ2pCLENBQUMsQ0FBQyxDQUFBO29CQUNOLENBQUMsQ0FBQyxFQUFBOzs7S0FDTDtJQUVELE1BQU07SUFDTyxxQ0FBSyxHQUFsQjs7Ozs7Ozt3QkFFUSxxQkFBTSxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUE7O3dCQUFwQixTQUFvQixDQUFBOzs7Ozs0QkFJeEIscUJBQU0sSUFBSSxDQUFDLE1BQU0sRUFBRSxFQUFBOzt3QkFBbkIsU0FBbUIsQ0FBQTs7Ozs7S0FDdEI7SUFDTCw0QkFBQztBQUFELENBQUMsQUF2YUQsQ0FBMkMsY0FBYyxHQXVheEQ7QUF2YVksc0RBQXFCIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiDnoJTnqbbpmL/ph4zkupHnmoRPVFPooajmoLzorr7orqHlsIZzZXNzaW9u44CBY2FjaGXjgIF1c2Vy5LiJ56eN57yT5a2Y5L+h5oGv566X5rOV5p6E5bu65Zyo5q2k5LmL5LiK44CCXG4gKi9cbmNvbnN0IFRhYmxlU3RvcmUgPSByZXF1aXJlKCd0YWJsZXN0b3JlJylcbmNvbnN0IExvbmcgPSBUYWJsZVN0b3JlLkxvbmdcbmNvbnN0IF8gPSByZXF1aXJlKCdsb2Rhc2gnKVxuXG5hYnN0cmFjdCBjbGFzcyBOb1NxbEludGVyZmFjZSB7XG4gICAgY29uc3RydWN0b3IodGFibGU6IHN0cmluZywgY29uZmlnOiBzdHJpbmcgfCB7fVtdID0gJ2RlZmF1bHQnKSB7XG4gICAgfVxuXG4gICAgLy8g57qm5a6a5omA5pyJ6KGo6YO96ZyA6KaB5pyJ6Ieq5aKe5Li76ZSuaWTkvZzkuLrlhoXpg6jllK/kuIDmoIfor4bnoIHvvIzlhbbkvZnkuInkuKrkuLvplK7lv4Xpobvlhajpg6jkuLrlrZfnrKbkuLLjgILvvIjlkI7nq6/pnIDopoHmianlsZVpbnTlkoxkYXRl5Z+65pys5pWw5o2u57G75Z6L5Lul5Y+K5pON5L2c56ym6YeN6L295a6e546w5LyY5YyW5byA5Y+R77yJXG4gICAgLy8g5bGe5oCn5a2X5q615Y+v5Lul5qC55o2u5Lia5Yqh5ryU5Y+Y5Lu75oSP55qE5omp5bGV5aKe5Yqg55Sx5Lia5Yqh5Luj56CB5a+55LqO6ICB5pWw5o2u5LiN5a2Y5Zyo5paw5aKe5a2X5q615YC855qE5oOF5Ya15YGa5YW85a655aSE55CGXG4gICAgcHVibGljIGFic3RyYWN0IGFzeW5jIGluc2VydChpZCwga3Z0OiB7IFtpbmRleDogc3RyaW5nXTogc3RyaW5nIHwgeyB2YWx1ZTogc3RyaW5nLCB0aW1lc3RhbXA6IG51bWJlciB9IH0pIDtcblxuICAgIC8vIOadoeS7tuabtOaWsOW/hemhu+imgeWhq+WGmeS4u+mUruiusOW9leWAvCjku4Xku4XlhYHorrjmm7TmlrDlsZ7mgKflrZfmrrXogIzkuLvplK7lrZfmrrXmmK/kuI3lhYHorrjmm7TmlrDnmoTvvIxcbiAgICAvLyAgICAgIOaVtOWIl+abtOaWsOWSjOWIoOmZpOWIl+WxnuS6jui/kOe7tOaTjeS9nOemgeatouW6lOeUqOS4reS9v+eUqOmcgOimgeWNleeLrOaOpeWPo+S7peWPiuadg+mZkOiupOivgSlcbiAgICBwdWJsaWMgYWJzdHJhY3QgYXN5bmMgdXBkYXRlKGlkOiBzdHJpbmcsIGt2dDogeyBbaW5kZXg6IHN0cmluZ106IHN0cmluZyB8IG51bGwgfCBudW1iZXIgfSkgO1xuXG4gICAgLy8g5Lul5Li76ZSu5L2c5Li65p2h5Lu25Yig6Zmk6K6w5b2V5YC8XG4gICAgcHVibGljIGFic3RyYWN0IGFzeW5jIGRlbGV0ZShpZDogc3RyaW5nKTtcblxuICAgIC8vIOWIhumhteafpeivouS4gOasoeajgOe0ok7mnaHorrDlvZVcbiAgICBwdWJsaWMgYWJzdHJhY3QgYXN5bmMgcXVlcnkoaWQ6IHN0cmluZywga2V5czogc3RyaW5nW10gfCBudWxsLCBtYXhfdmVyc2lvbjogbnVtYmVyKTogUHJvbWlzZTxhbnk+O1xuXG4gICAgLy8g6ZSA5q+B6KGoXG4gICAgcHVibGljIGFic3RyYWN0IGFzeW5jIGRlc3Ryb3koKTtcblxuICAgIC8vIOWIm+W7uuihqFxuICAgIHB1YmxpYyBhYnN0cmFjdCBhc3luYyBjcmVhdGUoKTtcblxuICAgIC8vIOafpeivouW9k+WJjeihqOeahOaPj+i/sOS/oeaBr1xuICAgIHB1YmxpYyBhYnN0cmFjdCBhc3luYyBkZXNjcmliZSgpO1xuXG4gICAgLy8g5Y+Y5pu06KGoXG4gICAgcHVibGljIGFic3RyYWN0IGFzeW5jIGNoYW5nZShwYXJhbToge1xuICAgICAgICBtYXhWZXJzaW9ucz86IG51bWJlciwgdGltZW91dFNlY29uZHM6IG51bWJlcixcbiAgICAgICAgcmVzZXJ2ZWRUaHJvdWdocHV0UmVhZD86IG51bWJlciwgcmVzZXJ2ZWRUaHJvdWdocHV0V3JpdGU/OiBudW1iZXJcbiAgICB9KTtcblxuICAgIC8vIOmHjee9ruihqFxuICAgIHB1YmxpYyBhYnN0cmFjdCBhc3luYyByZXNldCgpO1xufVxuXG4vLyBleHBvcnQgYXN5bmMgZnVuY3Rpb24gdGVzdCgpIHtcbi8vICAgICBjb25zdCBvdHMgPSBuZXcgTm9TcWxBbGl5dW5UYWJsZXN0b3JlKCdzZXNzaW9uX3Rlc3QzJylcbi8vICAgICAvLyBsZXQgb3V0ID0gYXdhaXQgb3RzLmNyZWF0ZSg2MClcbi8vICAgICAvLyBGSVhNRSDpppbmrKHliJvlu7rooajpnIDopoHmnInkuIDlrprnmoTns7vnu5/lu7bov5/ml7bpl7TvvIzpnIDopoHmlL7liLBpbnN0YWxs6L+H56iL5Lit6L+b6KGM57u05oqk44CCXG4vLyAgICAgLy8gbGV0IG91dCA9IGF3YWl0IG90cy5pbnNlcnQoJ3gnLCB7YTogJ2FhYScsIGI6IHt2YWx1ZTogJ2JiYmInLCB0aW1lc3RhbXA6IERhdGUubm93KCl9fSlcbi8vICAgICAvLyBsZXQgb3V0ID0gYXdhaXQgb3RzLnVwZGF0ZSgneCcsIHthOiAnYWFhMicsIGI6ICdiYmIyJ30pXG4vLyAgICAgLy8gbGV0IG91dCA9IGF3YWl0IG90cy51cGRhdGUoJ3gnLCB7YTogbnVsbCwgYjogMTUyOTY2NjA1MzEwMH0pXG4vLyAgICAgLy8gbGV0IG91dCA9IGF3YWl0IG90cy5kZWxldGUoJ3gnKVxuLy9cbi8vICAgICBsZXQgb3V0XG4vLyAgICAgLy8gb3V0ID0gYXdhaXQgb3RzLmluc2VydCgneCcsIHthOiAnYWFhJywgYjoge3ZhbHVlOiAnYmJiYicsIHRpbWVzdGFtcDogRGF0ZS5ub3coKX19KVxuLy8gICAgIC8vIG91dCA9IGF3YWl0IG90cy5xdWVyeSgneCcpICAvLyDlj5blhajpg6jnmoTlrZfmrrVcbi8vICAgICAvLyBvdXQgPSBhd2FpdCBvdHMucXVlcnkoJ3gnLCBbJ2EnXSkgIC8vIOWPlumDqOWIhuWtl+autVxuLy8gICAgIC8vIG91dCA9IGF3YWl0IG90cy5xdWVyeSgneScpICAvLyDlj5bkuI3lrZjlnKjnmoTkuLvplK5cbi8vXG4vLyAgICAgLy8gb3V0ID0gYXdhaXQgb3RzLmNyZWF0ZSgyNCo2MCo2MCwyKVxuLy8gICAgIC8vIG91dCA9IGF3YWl0IG90cy5pbnNlcnQoJ3onLCB7YTogJ2FhYScrRGF0ZSgpLCBiOiB7dmFsdWU6ICdiYmJiJytEYXRlKCksIHRpbWVzdGFtcDogRGF0ZS5ub3coKX19KVxuLy8gICAgIC8vIG91dCA9IGF3YWl0IG90cy51cGRhdGUoJ3onLCB7YTogJ2FhYScrRGF0ZSgpLCBiOiAnYmJiYicrRGF0ZSgpfSlcbi8vICAgICAvLyBvdXQgPSBhd2FpdCBvdHMucXVlcnkoJ3onLCBudWxsLCAyKVxuLy8gICAgIG91dCA9IGF3YWl0IG90cy5xdWVyeSgneicsIG51bGwsIDEpXG4vLyAgICAgLy8g55So5pyA5L2z5pa55qGI5Y675a6e546w5a2X5q61XG4vLyAgICAgcmV0dXJuIG91dFxuLy8gfVxuXG4vLyDlr7nkuo7pmL/ph4zkupF0YWJsZXN0b3Jl55qE5Y2V6KGo5Yqf6IO9566A5Y2V5bCB6KOF5aSE55CG77yI5LiA5Liq5Li76ZSu5bm25L2c5Li65YiG5Yy65ZSv5LiA5Yy65YiG5LiA5Liq6K6w5b2V77yM5LuF55So5LqO57yT5a2Y5Yqf6IO95a6e546w44CCXG4vLyDlsIHoo4XnibnngrnvvJrmr4/kuKrorrDlvZXkuIDmnaHkuLvplK7lubbkvZzkuLrliIbljLrvvIzmr4/kuKrorrDlvZXmnInml6DmlbDkuKprduWAvOWPr+S+m+mFjee9ru+8jOavj+S4qmt25YC86YO95pyJ5LiA5Liq5pe26Ze05oiz6LaF5pe25pe26Ze05Zyo6KGo5LiK5Y2V54us6YWN572u44CC77yJXG5leHBvcnQgY2xhc3MgTm9TcWxBbGl5dW5UYWJsZXN0b3JlIGV4dGVuZHMgTm9TcWxJbnRlcmZhY2Uge1xuICAgIHByaXZhdGUgX3RhYmxlOiBzdHJpbmdcbiAgICBwcml2YXRlIF9jbGllbnQ6IGFueVxuICAgIHByaXZhdGUgX3NjaGVtYTogYW55XG5cbiAgICBzZXQgdGFibGUodmFsdWUpIHtcbiAgICAgICAgdGhpcy5fdGFibGUgPSB2YWx1ZVxuICAgIH1cblxuICAgIGNvbnN0cnVjdG9yKHRhYmxlOiBzdHJpbmcsIGNvbmZpZzogc3RyaW5nID0gJ2RlZmF1bHQnKSB7XG4gICAgICAgIHN1cGVyKHRhYmxlLCBjb25maWcpXG4g