@cn-shell/aws-utils
Version:
A Cloud Native extension for AWS
520 lines (519 loc) • 15.7 kB
JavaScript
;
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;