UNPKG

@codification/cutwater-aws

Version:

A library providing general functionality for TypeScript based AWS projects.

258 lines 11.1 kB
"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