UNPKG

@cn-shell/aws-utils

Version:
520 lines (519 loc) 15.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; }, }); } : function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; }); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); } : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function(mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function(mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Table = exports.CriteriaOperators = void 0; // imports here const Aws = __importStar(require("./aws-base")); const dynamodb_1 = __importDefault(require("aws-sdk/clients/dynamodb")); // import AWS from "aws-sdk/global"; // AWS.config.logger = console; // DynamoDB consts here const DDB_API_VER = "2012-08-10"; const DDB_COUNTER = "counter"; // Consts here exports.CriteriaOperators = { EQ: "EQ", NE: "NE", LE: "LE", LT: "LT", GE: "GE", GT: "GT", BETWEEN: "BETWEEN", BEGINS_WITH: "BEGINS_WITH", EXISTS: "EXISTS", NOT_EXISTS: "NOT_EXISTS", }; // Class Table here class Table extends Aws.Base { // Constructor here constructor(name, opts) { super(name, opts); this._table = opts.table; this._tableIndex = opts.tableIndex; this._partitionKey = opts.partitionKey; this._sortKey = opts.sortKey; // Create AWS service objects this.documentClient = new dynamodb_1.default.DocumentClient({ region: this._region, apiVersion: DDB_API_VER, }); } // Public and Private methods here async start() { return true; } async stop() {} async healthCheck() { return true; } // code = "ThrottlingException" async putItem(item) { let params = { TableName: this._table, Item: item, }; let success = true; await this.documentClient .put(params) .promise() .catch(e => { this.error( "putIem (Table: %s) Error: (%s: %s).", this._table, e.code, e, params, ); success = false; }); return success; } async putItems(items) { let params = { RequestItems: { [this._table]: [], }, }; for (let item of items) { params.RequestItems[this._table].push({ PutRequest: { Item: item } }); } let success = true; await this.documentClient .batchWrite(params) .promise() .catch(e => { this.error( "putIems (Table: %s) Error: (%s: %s).", this._table, e.code, e, params, ); success = false; }); // TODO: Need to ensure there are no more then 25 items being passed // and check for unprocessed items return success; } async getItem(key) { let params = { TableName: this._table, Key: { [this._partitionKey]: key.partitionKeyValue, }, }; if (this._sortKey !== undefined && key.sortKeyValue !== undefined) { params.Key[this._sortKey] = key.sortKeyValue; } let item = await this.documentClient .get(params) .promise() .catch(e => { this.error( "getItem (Table: %s) Error: (%s: %s).", this._table, e.code, e, params, ); }); return item; } async deleteItem(key) { let params = { TableName: this._table, Key: { [this._partitionKey]: key.partitionKeyValue, }, }; if (this._sortKey !== undefined && key.sortKeyValue !== undefined) { params.Key[this._sortKey] = key.sortKeyValue; } let success = true; await this.documentClient .delete(params) .promise() .catch(e => { this.error( "deleteItem (Table: %s) Error: (%s: %s).", this._table, e.code, e, params, ); success = false; }); return success; } async query(query) { let values = { ":pval": query.partitionKeyValue, }; let names = { "#pkey": this._partitionKey, }; let expression = "#pkey = :pval"; if (this._sortKey !== undefined && query.sortCriteria !== undefined) { names["#skey"] = this._sortKey; switch (query.sortCriteria.operator) { case exports.CriteriaOperators.EQ: expression += " and #skey = :sval"; values[":sval"] = query.sortCriteria.value; break; case exports.CriteriaOperators.NE: expression += " and #skey <> :sval"; values[":sval"] = query.sortCriteria.value; break; case exports.CriteriaOperators.LE: expression += " and #skey <= :sval"; values[":sval"] = query.sortCriteria.value; break; case exports.CriteriaOperators.LT: expression += " and #skey < :sval"; values[":sval"] = query.sortCriteria.value; break; case exports.CriteriaOperators.GE: expression += " and #skey >= :sval"; values[":sval"] = query.sortCriteria.value; break; case exports.CriteriaOperators.GT: expression += " and #skey > :sval"; values[":sval"] = query.sortCriteria.value; break; case exports.CriteriaOperators.BETWEEN: if (query.sortCriteria.between !== undefined) { expression += " and #skey between :low AND :high"; values[":low"] = query.sortCriteria.between.low; values[":high"] = query.sortCriteria.between.high; } break; case exports.CriteriaOperators.BEGINS_WITH: expression += " and begins_with(#skey, :sval)"; values[":sval"] = query.sortCriteria.value; break; } } let attributes = ""; if (query.attributes !== undefined) { let name = "a"; let nameCode = name.charCodeAt(0); for (let attrib of query.attributes) { name = String.fromCharCode(nameCode); if (name !== "a") { attributes += ","; } names[`#${name}`] = attrib; attributes += `#${name}`; nameCode++; } } let params = { TableName: this._table, IndexName: this._tableIndex, ScanIndexForward: query.reverseQuery ? false : true, Limit: query.limit, ExclusiveStartKey: query.nextKey, KeyConditionExpression: expression, ExpressionAttributeValues: values, ExpressionAttributeNames: names, }; if (attributes.length) { params.ProjectionExpression = attributes; } this.debug("%j", params); return this.documentClient.query(params).promise(); } async updateItem(item) { let values = {}; let names = {}; let expression = ""; let name = "a"; let nameCode = name.charCodeAt(0); if (item.set !== undefined) { expression = "SET"; for (let key in item.set) { // If the value is undefined then skip this or DDB will spit the dummy if (item.set[key] === undefined) { continue; } name = String.fromCharCode(nameCode); if (name !== "a") { expression += ","; } // Check if this is a map or not (a map will contain '.'s) let map = key.split("."); if (map.length === 1) { names[`#${name}`] = key; values[`:${name}`] = item.set[key]; expression += ` #${name} = :${name}`; } else { let first = map.slice(0, map.length - 1).join("."); let second = map.slice(-1)[0]; names[`#${name}`] = second; values[`:${name}`] = item.set[key]; expression += ` ${first}.#${name} = :${name}`; } nameCode++; } } if (item.add !== undefined) { if (expression.length === 0) { expression = "SET"; } for (let key in item.add) { // If the value is undefined then skip this or DDB will spit the dummy if (item.add[key] === undefined) { continue; } name = String.fromCharCode(nameCode); if (name !== "a") { expression += ","; } // Check if this is a map or not (a map will contain '.'s) let map = key.split("."); if (map.length === 1) { names[`#${name}`] = key; values[`:${name}`] = item.add[key]; expression += ` #${name} = #${name} + :${name}`; } else { let first = map.slice(0, map.length - 1).join("."); let second = map.slice(-1)[0]; names[`#${name}`] = second; values[`:${name}`] = item.add[key]; expression += ` ${first}.#${name} = ${first}.#${name} + :${name}`; } nameCode++; } } if (item.append !== undefined) { if (expression.length === 0) { expression = "SET"; } for (let key in item.append) { // If the value is undefined then skip this or DDB will spit the dummy if (item.append[key] === undefined) { continue; } name = String.fromCharCode(nameCode); if (name !== "a") { expression += ","; } // Check if this is a map or not (a map will contain '.'s) let map = key.split("."); if (map.length === 1) { names[`#${name}`] = key; values[`:${name}`] = item.append[key]; expression += ` #${name} = list_append(#${name}, :${name})`; } else { let first = map.slice(0, map.length - 1).join("."); let second = map.slice(-1)[0]; names[`#${name}`] = second; values[`:${name}`] = item.append[key]; expression += ` ${first}.#${name} = list_append(${first}.#${name}, :${name})`; } nameCode++; } } let startName = String.fromCharCode(nameCode); if (item.remove !== undefined && item.remove.length) { expression += " REMOVE"; for (let key of item.remove) { name = String.fromCharCode(nameCode); if (name !== startName) { expression += ","; } // Check if this is a map or not (a map will contain '.'s) let map = key.split("."); if (map.length === 1) { names[`#${name}`] = key; expression += ` #${name} `; } else { let first = map.slice(0, map.length - 1).join("."); let second = map.slice(-1)[0]; names[`#${name}`] = second; expression += ` ${first}.#${name}`; } nameCode++; } } let conditionExpression = ""; let conditions = []; if (item.conditions !== undefined) { conditions = item.conditions; } for (let condition of conditions) { if (conditionExpression.length) { conditionExpression += " and "; } let name = String.fromCharCode(nameCode); names[`#${name}`] = condition.attribute; switch (condition.condition) { case exports.CriteriaOperators.EXISTS: conditionExpression += `attribute_exists(#${name})`; break; case exports.CriteriaOperators.NOT_EXISTS: conditionExpression += `attribute_not_exists(#${name})`; break; case exports.CriteriaOperators.EQ: conditionExpression += `#${name} = :${name}`; values[`:${name}`] = condition.value; break; case exports.CriteriaOperators.NE: conditionExpression += `#${name} <> :${name}`; values[`:${name}`] = condition.value; break; case exports.CriteriaOperators.LE: conditionExpression += `#${name} <= :${name}`; values[`:${name}`] = condition.value; break; case exports.CriteriaOperators.LT: conditionExpression += `#${name} < :${name}`; values[`:${name}`] = condition.value; break; case exports.CriteriaOperators.GE: conditionExpression += `#${name} >= :${name}`; values[`:${name}`] = condition.value; break; case exports.CriteriaOperators.GT: conditionExpression += `#${name} > :${name}`; values[`:${name}`] = condition.value; break; case exports.CriteriaOperators.BETWEEN: if (condition.between !== undefined) { conditionExpression += `#${name} between :low AND :high`; values[":low"] = condition.between.low; values[":high"] = condition.between.high; } break; } nameCode++; } let params = { TableName: this._table, Key: { [this._partitionKey]: item.key.partitionKeyValue, }, ExpressionAttributeNames: names, UpdateExpression: expression, ReturnValues: item.returnUpdated, }; if (this._sortKey !== undefined) { params.Key[this._sortKey] = item.key.sortKeyValue; } if (conditionExpression.length !== 0) { params.ConditionExpression = conditionExpression; } if (Object.entries(values).length) { params.ExpressionAttributeValues = values; } this.debug("%j", params); let success = true; let res = await this.documentClient .update(params) .promise() .catch(e => { success = false; // Check if this is just because the condition failed if ( e.code === "ConditionalCheckFailedException" && item.conditions !== undefined ) { return; } this.error( "updateItem (Table: %s) Error: (%s: %s). Params: (%j)", this._table, e.code, e, params, ); }); if (success && item.returnUpdated !== undefined && res !== undefined) { return res.Attributes; } return success; } async getNextAtomicCounter(counter, counterKey = DDB_COUNTER) { // Update the current value - if the counter exists let upParams = { key: { partitionKeyValue: counterKey, sortKeyValue: counter }, add: { counter: 1 }, conditions: [ { attribute: counterKey, condition: "EXISTS", }, ], returnUpdated: "UPDATED_NEW", }; let attribs = await this.updateItem(upParams); if (typeof attribs === "object" && attribs[counterKey] !== undefined) { // return next value return attribs[counterKey].toString(); } // Otherwise - create the counter and set it to 1 upParams = { key: { partitionKeyValue: counterKey, sortKeyValue: counter }, set: { [counterKey]: 1 }, conditions: [ { attribute: counterKey, condition: "NOT_EXISTS", }, ], }; let created = await this.updateItem(upParams); if (created === false) { return ""; } return "1"; } async scan() { let params = { TableName: this._table, }; return this.documentClient.scan(params).promise(); } } exports.Table = Table;