@codification/cutwater-aws
Version:
A library providing general functionality for TypeScript based AWS projects.
258 lines • 11.1 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.CompoundItemRepository = void 0;
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
const cutwater_core_1 = require("@codification/cutwater-core");
const cutwater_logging_1 = require("@codification/cutwater-logging");
const DynamoItem_1 = require("../DynamoItem");
const CompoundKey_1 = require("./CompoundKey");
class CompoundItemRepository {
constructor(config) {
this.config = config;
this.LOG = cutwater_logging_1.LoggerFactory.getLogger();
this.db = new client_dynamodb_1.DynamoDB();
this.db = config.db || new client_dynamodb_1.DynamoDB();
}
get itemType() {
return this.config.itemType;
}
getAll(parentId) {
return __awaiter(this, void 0, void 0, function* () {
const partitionValue = parentId ? CompoundKey_1.CompoundKey.toPartitionKey(parentId) : CompoundKey_1.CompoundKey.DEFAULT_PARENT;
const results = yield this.getAllMaps(partitionValue);
const rval = [];
for (const item of results) {
rval.push(this.attributeMapToItem(item));
}
return rval;
});
}
get(id) {
return __awaiter(this, void 0, void 0, function* () {
const params = this.toItemInput(id);
this.LOG.trace('DynamoDB Get: ', params);
const result = yield this.db.getItem(params);
if (!result.Item) {
this.LOG.trace('No result.');
}
return result.Item ? this.attributeMapToItem(result.Item) : undefined;
});
}
toId(item) {
if (item instanceof DynamoItem_1.DynamoItem) {
return CompoundKey_1.CompoundKey.fromAttributeMap(item.item).compoundItemId.itemId;
}
return item[this.config.idProperty];
}
refreshItem(item) {
return __awaiter(this, void 0, void 0, function* () {
return this.attributeMapToItem(yield this.itemToAttributeMap(item));
});
}
put(item) {
return __awaiter(this, void 0, void 0, function* () {
const attributeMap = yield this.itemToAttributeMap(item);
const params = Object.assign({ Item: attributeMap }, this.baseInput);
this.LOG.trace('DynamoDB Put: ', params);
yield this.db.putItem(params);
return this.attributeMapToItem(attributeMap);
});
}
putAll(items) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.batchPutOrDelete(items);
const rval = [];
for (const val of result) {
rval.push(yield this.refreshItem(val));
}
return rval;
});
}
remove(id) {
return __awaiter(this, void 0, void 0, function* () {
const rval = yield this.get(id);
if (rval) {
const params = this.toItemInput(id);
this.LOG.trace('DynamoDB Delete: ', params);
yield this.db.deleteItem(params);
}
return rval;
});
}
removeAll(ids) {
return __awaiter(this, void 0, void 0, function* () {
const keys = ids.map((id) => CompoundKey_1.CompoundKey.fromItemId(this.itemType, id));
return (yield this.batchPutOrDelete(keys)).map((key) => key.compoundItemId.itemId);
});
}
toItemInput(id) {
const key = CompoundKey_1.CompoundKey.fromItemId(this.itemType, id);
const rval = Object.assign({ Key: {
[this.config.partitionKey]: {
S: key.partitionKey,
},
[this.config.sortKey]: {
S: key.sortKey,
},
} }, this.baseInput);
return rval;
}
get baseInput() {
return {
TableName: this.config.tableName,
};
}
attributeMapToItem(map) {
const dynamoItem = new DynamoItem_1.DynamoItem(map);
const idObj = {
[this.config.idProperty]: this.toId(dynamoItem),
};
return Object.assign(this.config.toItem(map), idObj);
}
itemToAttributeMap(item) {
return __awaiter(this, void 0, void 0, function* () {
const key = CompoundKey_1.CompoundKey.fromItemId(this.itemType, this.toId(item));
const rval = new DynamoItem_1.DynamoItem(this.config.toAttributeMap(item));
rval.setString(this.config.partitionKey, key.partitionKey);
rval.setString(this.config.sortKey, key.sortKey);
return rval.item;
});
}
getAllMaps(partitionValue, cursor) {
return __awaiter(this, void 0, void 0, function* () {
const params = Object.assign({ ExpressionAttributeValues: {
':partitionValue': {
S: partitionValue,
},
':itemType': {
S: this.config.itemType,
},
}, KeyConditionExpression: `${this.config.partitionKey} = :partitionValue and begins_with(${this.config.sortKey}, :itemType)` }, this.baseInput);
if (cursor) {
params.ExclusiveStartKey = {
[this.config.partitionKey]: { S: partitionValue },
[this.config.sortKey]: { S: cursor },
};
}
this.LOG.trace('DynamoDB GetAll: ', params);
let result;
try {
result = yield this.db.query(params);
if (!result || !result.Items) {
return [];
}
}
catch (err) {
this.LOG.error(`Encountered error during query[${JSON.stringify(params)}]: `, err);
return [];
}
const rval = result.Items;
if (result.LastEvaluatedKey && result.LastEvaluatedKey[this.config.sortKey].S) {
const moreResults = yield this.getAllMaps(partitionValue, result.LastEvaluatedKey[this.config.sortKey].S);
for (const m of moreResults) {
rval.push(m);
}
}
return rval;
});
}
batchPutOrDelete(requests) {
return __awaiter(this, void 0, void 0, function* () {
const inputs = yield this.toBatchWriteInputItems(requests);
const unprocessed = [];
for (const input of inputs) {
unprocessed.push(...(yield this.batchWithRetry(input)));
}
return this.removeUnprocessed(requests, unprocessed);
});
}
removeUnprocessed(requests, unprocessed) {
return __awaiter(this, void 0, void 0, function* () {
if (unprocessed.length < 1) {
return requests;
}
if (requests[0].partitionKey !== undefined) {
const failIds = unprocessed.map((key) => CompoundKey_1.CompoundKey.fromAttributeMap(key).compoundItemId.itemId);
return requests.filter((key) => !failIds.includes(key.compoundItemId.itemId));
}
else {
const failIds = unprocessed.map((map) => CompoundKey_1.CompoundKey.fromAttributeMap(map).compoundItemId.itemId);
return requests.filter((item) => !failIds.includes(this.toId(item)));
}
});
}
findWriteRequests(input, tableName) {
const rval = input.RequestItems && input.RequestItems[tableName];
if (!rval) {
throw new Error(`Write requests missing for table: ${tableName}`);
}
return rval;
}
toBatchWriteInputItems(itemsOrKeys) {
return __awaiter(this, void 0, void 0, function* () {
const tableName = this.config.tableName;
const writeRequests = yield this.toWriteRequests(itemsOrKeys);
const rval = writeRequests.reduce((inputs, req, index) => {
if (index % 24 === 0) {
inputs.push({ RequestItems: { [tableName]: [] } });
}
const current = inputs[inputs.length - 1] && inputs[inputs.length - 1];
this.findWriteRequests(current, tableName).push(req);
return inputs;
}, []);
return rval;
});
}
toWriteRequests(itemsOrKeys) {
return __awaiter(this, void 0, void 0, function* () {
const rval = [];
for (const val of itemsOrKeys) {
rval.push(yield this.toWriteRequest(val));
}
return rval;
});
}
toWriteRequest(itemOrKey) {
return __awaiter(this, void 0, void 0, function* () {
return itemOrKey instanceof CompoundKey_1.CompoundKey
? { DeleteRequest: { Key: itemOrKey.toKey() } }
: { PutRequest: { Item: yield this.itemToAttributeMap(itemOrKey) } };
});
}
batchWithRetry(input, maxTries = 10, currentTry = 0) {
return __awaiter(this, void 0, void 0, function* () {
const tableName = this.config.tableName;
const rval = [];
if (currentTry === maxTries) {
const remaining = this.findWriteRequests(input, this.config.tableName);
if (remaining[0].Key !== undefined) {
return remaining.map((req) => { var _a; return (_a = req.DeleteRequest) === null || _a === void 0 ? void 0 : _a.Key; });
}
else {
return remaining.map((req) => { var _a; return (_a = req.PutRequest) === null || _a === void 0 ? void 0 : _a.Item; });
}
}
const result = yield this.db.batchWriteItem(input);
const unprocessed = (result.UnprocessedItems && result.UnprocessedItems[tableName]) || [];
if (unprocessed.length > 0) {
currentTry++;
yield cutwater_core_1.AsyncUtils.wait(Math.pow(2, currentTry) * 10);
rval.push(...(yield this.batchWithRetry({
RequestItems: { [tableName]: unprocessed },
}, maxTries, currentTry)));
}
return rval;
});
}
}
exports.CompoundItemRepository = CompoundItemRepository;
//# sourceMappingURL=CompoundItemRespository.js.map