UNPKG

dynamodb-fast-access

Version:

Default CRUD operations for managing AWS DynamoDB table items in an easy-to-extend structure.

399 lines (398 loc) 19.2 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 }); const ExpressionCreator_1 = require("./ExpressionCreator"); const DatabaseConfig_1 = require("../util/DatabaseConfig"); const Extender_1 = require("./Extender"); const RelatedDeleter_1 = require("./RelatedDeleter"); const DynamoDBFastAccessError_1 = require("../util/DynamoDBFastAccessError"); function DB(tableAlias, extend = Extender_1.DefaultExtender, deleteRelated = RelatedDeleter_1.DefaultRelatedDeleter) { return class DB { static getTableConfig() { const tableConfig = DatabaseConfig_1.DatabaseConfig.DynamoDBConfig.tables.find(x => x.tableAlias === tableAlias); if (tableConfig === undefined) { throw new DynamoDBFastAccessError_1.DynamoDBFastAccessError('Table configuration with the given alias does not exist.'); } return tableConfig; } static getSortKeySeparator() { const sortKeySeparator = DB.getTableConfig().sortKeySeparator; return sortKeySeparator === undefined ? '$' : sortKeySeparator; } static getTableAlias() { return DB.getTableConfig().tableAlias; } /** * Returns the name of the table. */ static getTableName() { return DB.getTableConfig().tableName; } static getPartitionKeyName() { return DB.getTableConfig().partitionKeyName; } static getPartitionKeyType() { return DB.getTableConfig().partitionKeyType; } static getSortKeyName() { return DB.getTableConfig().sortKeyName; } static getSortKeyType() { return DB.getTableConfig().sortKeyType; } static extend(rawEntities) { return extend(rawEntities); } static deleteRelated(ids) { return deleteRelated(ids); } static getByIdRaw(id, attributes) { return __awaiter(this, void 0, void 0, function* () { const tableName = DB.getTableName(); const partitionKeyName = DB.getPartitionKeyName(); const sortKeyName = DB.getSortKeyName(); const sortKeySeparator = DB.getSortKeySeparator(); const params = { TableName: tableName, Key: { [partitionKeyName]: DB._castKey(id).partitionKey }, }; if (attributes) { params.ConsistentRead = attributes.ConsistentRead; } if (sortKeyName !== undefined) { if (id.indexOf(sortKeySeparator) < 0) { throw new DynamoDBFastAccessError_1.DynamoDBFastAccessError('Composite key must include ' + sortKeySeparator + ' that separates the keys.'); } params.Key[partitionKeyName] = DB._castKey(id).partitionKey; params.Key[sortKeyName] = DB._castKey(id).sortKey; } const entityData = yield DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient().get(params).promise(); if (!entityData.Item) throw new DynamoDBFastAccessError_1.DynamoDBFastAccessError('Resource does not exist.'); return entityData.Item; }); } static getById(id, attributes) { return __awaiter(this, void 0, void 0, function* () { const rawEntity = yield DB.getByIdRaw(id, attributes); return (yield extend([rawEntity]))[0]; }); } static getByIdsRaw(ids) { return __awaiter(this, void 0, void 0, function* () { if (ids.length === 0) return []; const tableName = DB.getTableName(); const partitionKeyName = DB.getPartitionKeyName(); const sortKeyName = DB.getSortKeyName(); const sortKeySeparator = DB.getSortKeySeparator(); const entityIDsParts = []; const partsSize = 100; for (let i = 0, j = ids.length; i < j; i += partsSize) { entityIDsParts.push(ids.slice(i, i + partsSize)); } const dataArray = yield Promise.all(entityIDsParts.map(entityIDsPart => { const params = { RequestItems: { [tableName]: { Keys: [], }, }, }; for (const entityID of entityIDsPart) { if (sortKeyName === undefined) { params.RequestItems[tableName].Keys.push({ [partitionKeyName]: entityID, }); } else { if (entityID.indexOf(sortKeySeparator) < 0) { throw new DynamoDBFastAccessError_1.DynamoDBFastAccessError('Composite key must include ' + sortKeySeparator + ' that separates the keys.'); } params.RequestItems[tableName].Keys.push({ [partitionKeyName]: DB._castKey(entityID).partitionKey, [sortKeyName]: DB._castKey(entityID).sortKey, }); } } return DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient().batchGet(params).promise(); })); let rawEntities = []; for (const data of dataArray) { rawEntities = rawEntities.concat(data.Responses[tableName]); } return rawEntities; }); } static getByIds(ids) { return __awaiter(this, void 0, void 0, function* () { const rawEntities = yield DB.getByIdsRaw(ids); return extend(rawEntities); }); } static scanFilteredRaw(filterAttributes, arrayContainsAttribute) { const scanParams = DB._buildScanParams(filterAttributes, arrayContainsAttribute); return DB._scanRaw(scanParams, []); } static scanFiltered(filterAttributes, arrayContainsAttribute) { return __awaiter(this, void 0, void 0, function* () { const rawEntities = yield DB.scanFilteredRaw(filterAttributes, arrayContainsAttribute); return extend(rawEntities); }); } static _scanRaw(params, items) { return __awaiter(this, void 0, void 0, function* () { const data = yield DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient().scan(params).promise(); if (data.Items) items = items.concat(data.Items); if (!data.LastEvaluatedKey) return items; params.ExclusiveStartKey = data.LastEvaluatedKey; return DB._scanRaw(params, items); }); } static _scan(params, items) { return __awaiter(this, void 0, void 0, function* () { const data = yield DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient().scan(params).promise(); if (data.Items) items = items.concat(data.Items); if (!data.LastEvaluatedKey) return extend(items); params.ExclusiveStartKey = data.LastEvaluatedKey; return extend(yield DB._scanRaw(params, items)); }); } static deleteById(id) { return __awaiter(this, void 0, void 0, function* () { const tableName = DB.getTableName(); const partitionKeyName = DB.getPartitionKeyName(); const sortKeyName = DB.getSortKeyName(); const sortKeySeparator = DB.getSortKeySeparator(); const params = { TableName: tableName, Key: { [partitionKeyName]: DB._castKey(id).partitionKey }, }; if (sortKeyName !== undefined) { if (id.indexOf(sortKeySeparator) < 0) { throw new DynamoDBFastAccessError_1.DynamoDBFastAccessError('Composite key must include ' + sortKeySeparator + ' that separates the keys.'); } params.Key[partitionKeyName] = DB._castKey(id).partitionKey; params.Key[sortKeyName] = DB._castKey(id).sortKey; } yield deleteRelated([id]); yield DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient() .delete(params) .promise() .catch((err) => { if (err.name !== 'ResourceNotFoundException') throw err; else return id; }); return id; }); } static deleteByIds(ids) { return __awaiter(this, void 0, void 0, function* () { if (!ids || ids.length === 0) return []; const tableName = DB.getTableName(); const partitionKeyName = DB.getPartitionKeyName(); const sortKeyName = DB.getSortKeyName(); const entityIDsPartsArray = []; const partsSize = 25; for (let i = 0, j = ids.length; i < j; i += partsSize) { entityIDsPartsArray.push(ids.slice(i, i + partsSize)); } for (const entityIDsParts of entityIDsPartsArray) { const batchWriteParams = { RequestItems: { [tableName]: [], }, }; for (const entityIDsPart of entityIDsParts) { batchWriteParams.RequestItems[tableName].push({ DeleteRequest: { Key: sortKeyName === undefined ? { [partitionKeyName]: entityIDsPart } : { [partitionKeyName]: DB._castKey(entityIDsPart).partitionKey, [sortKeyName]: DB._castKey(entityIDsPart).sortKey, }, }, }); } yield deleteRelated(entityIDsParts); yield DB._batchWriteWithRetry(batchWriteParams, 0); } return ids; }); } static deleteScanFiltered(filterAttributes, arrayContains) { return __awaiter(this, void 0, void 0, function* () { const scanParams = DB._buildScanParams(filterAttributes, arrayContains); return DB._scanDelete(scanParams); }); } static _buildScanParams(filterAttributes, arrayContainsAttribute) { const tableName = DB.getTableName(); let FilterExpression = ''; let ExpressionAttributeNames = {}; let ExpressionAttributeValues = {}; if (filterAttributes) { FilterExpression = ExpressionCreator_1.ExpressionCreator.getFilterExpression(filterAttributes); ExpressionAttributeNames = ExpressionCreator_1.ExpressionCreator.getExpressionAttributeNames(filterAttributes); ExpressionAttributeValues = ExpressionCreator_1.ExpressionCreator.getExpressionAttributeValues(filterAttributes); } if (arrayContainsAttribute && arrayContainsAttribute.arrayName && arrayContainsAttribute.value) { FilterExpression += ' contains(#' + arrayContainsAttribute.arrayName + ', :attributeValue)'; ExpressionAttributeNames['#' + arrayContainsAttribute.arrayName] = arrayContainsAttribute.arrayName; ExpressionAttributeValues[':attributeValue'] = arrayContainsAttribute.value; } const params = { TableName: tableName, }; if (FilterExpression !== '') params.FilterExpression = FilterExpression; if (Object.keys(ExpressionAttributeNames).length > 0) { params.ExpressionAttributeNames = ExpressionAttributeNames; } if (Object.keys(ExpressionAttributeValues).length > 0) { params.ExpressionAttributeValues = ExpressionAttributeValues; } return params; } static _scanDelete(params) { return __awaiter(this, void 0, void 0, function* () { const partitionKeyName = DB.getPartitionKeyName(); const sortKeyName = DB.getSortKeyName(); const sortKeySeparator = DB.getSortKeySeparator(); const data = yield DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient().scan(params).promise(); if (data.Items) { const rawEntities = data.Items; yield Promise.all(rawEntities.map((rawEntity) => __awaiter(this, void 0, void 0, function* () { const entityId = sortKeyName === undefined ? rawEntity[partitionKeyName] : rawEntity[partitionKeyName] + sortKeySeparator + rawEntity[sortKeyName]; yield deleteRelated([entityId]); yield DB.deleteById(entityId); }))); } if (!data.LastEvaluatedKey) return {}; params.ExclusiveStartKey = data.LastEvaluatedKey; return DB._scanDelete(params); }); } static createRaw(entity) { return __awaiter(this, void 0, void 0, function* () { const tableName = DB.getTableName(); const attributeMap = entity; const params = { TableName: tableName, Item: attributeMap, }; yield DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient().put(params).promise(); return attributeMap; }); } static create(entity) { return __awaiter(this, void 0, void 0, function* () { const attributeMap = yield DB.createRaw(entity); const extended = yield extend([attributeMap]); return extended[0]; }); } static createBatchRaw(entities) { return __awaiter(this, void 0, void 0, function* () { const putRequests = []; for (const entity of entities) { putRequests.push({ PutRequest: { Item: entity, }, }); } const putRequestParts = []; const partsSize = 25; for (let i = 0, j = putRequests.length; i < j; i += partsSize) { putRequestParts.push(putRequests.slice(i, i + partsSize)); } for (const putRequestPart of putRequestParts) { const batchWriteParams = { RequestItems: { [DB.getTableName()]: putRequestPart, }, }; yield DB._batchWriteWithRetry(batchWriteParams, 0); } return entities; }); } static createBatch(entities) { return __awaiter(this, void 0, void 0, function* () { return extend(yield DB.createBatchRaw(entities)); }); } /** * @params: The parameter for the batchWrite operation. * @retry: This should be 0 when called first, this will be incremented in the recursion. */ static _batchWriteWithRetry(params, retry) { return __awaiter(this, void 0, void 0, function* () { if (retry > 0) console.log('Retry: ' + retry); if (retry > DatabaseConfig_1.DatabaseConfig.DynamoDBConfig.maxRetries) { throw new DynamoDBFastAccessError_1.DynamoDBFastAccessError('Maximum number of batch write retries exceeded.'); } const data = yield DatabaseConfig_1.DatabaseConfig.DynamoDBDocumentClient().batchWrite(params).promise(); if (data.UnprocessedItems && Object.keys(data.UnprocessedItems).length > 0) { const waitTimeout = Math.pow(2, retry) * 50 * Math.random(); params.RequestItems = data.UnprocessedItems; yield new Promise(res => setTimeout(res, waitTimeout)); yield DB._batchWriteWithRetry(params, retry + 1); } return {}; }); } static _castKey(key) { const partitionKey = key.split(DB.getSortKeySeparator())[0]; const sortKey = key.split(DB.getSortKeySeparator()).splice(1).join(DB.getSortKeySeparator()); let partitionKeyTyped; switch (DB.getPartitionKeyType()) { case 'string': partitionKeyTyped = partitionKey; break; case 'number': partitionKeyTyped = +partitionKey; break; } if (!DB.getSortKeyName()) return { partitionKey: partitionKeyTyped }; let sortKeyTyped; switch (DB.getSortKeyType()) { case 'string': sortKeyTyped = sortKey; break; case 'number': sortKeyTyped = +sortKey; break; } return { partitionKey: partitionKeyTyped, sortKey: sortKeyTyped, }; } }; } exports.DB = DB;