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
JavaScript
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;
;