@mbc-cqrs-serverless/core
Version:
CQRS and event base core
324 lines • 13.2 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var DynamoDbService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DynamoDbService = void 0;
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const ulid_1 = require("ulid");
const helpers_1 = require("../helpers");
const object_1 = require("../helpers/object");
const s3_service_1 = require("./s3.service");
const CLIENT_INSTANCE = Symbol();
let DynamoDbService = DynamoDbService_1 = class DynamoDbService {
constructor(config, s3Service) {
this.config = config;
this.s3Service = s3Service;
this.logger = new common_1.Logger(DynamoDbService_1.name);
this[CLIENT_INSTANCE] = new client_dynamodb_1.DynamoDBClient({
endpoint: config.get('DYNAMODB_ENDPOINT'),
region: config.get('DYNAMODB_REGION'),
});
this.tablePrefix = `${config.get('NODE_ENV')}-${config.get('APP_NAME')}-`;
}
get client() {
return this[CLIENT_INSTANCE];
}
async putItem(tableName, item, conditions) {
const data = await this.objToDdbItem(tableName, item);
await this.client.send(new client_dynamodb_1.PutItemCommand({
TableName: tableName,
Item: data,
ConditionExpression: conditions,
ReturnValues: 'NONE',
}));
return item;
}
async updateItem(tableName, key, item, conditions) {
if (!item.set?.updatedAt) {
const updatedAt = new Date();
if (!item.set) {
item.set = { updatedAt };
}
else {
item.set.updatedAt = updatedAt;
}
}
const { updateExpression, expressionAttributeNames, expressionAttributeValues, } = this.buildUpdateExpression(item);
this.logger.debug(updateExpression);
this.logger.debug(expressionAttributeNames);
this.logger.debug(expressionAttributeValues);
const res = await this.client.send(new client_dynamodb_1.UpdateItemCommand({
TableName: tableName,
Key: this.toDdbKey(key),
UpdateExpression: updateExpression,
ExpressionAttributeNames: expressionAttributeNames,
ExpressionAttributeValues: await this.objToDdbItem(tableName, expressionAttributeValues),
ConditionExpression: conditions,
ReturnValues: 'ALL_NEW',
}));
return this.ddbItemToObj(res.Attributes);
}
async getItem(tableName, key) {
const { Item } = await this.client.send(new client_dynamodb_1.GetItemCommand({
TableName: tableName,
Key: this.toDdbKey(key),
}));
return await this.ddbItemToObj(Item);
}
async listItemsByPk(tableName, pk, sk, startFromSk, limit = 10, order = 'asc') {
const res = await this.client.send(new client_dynamodb_1.QueryCommand({
TableName: tableName,
Limit: limit,
ScanIndexForward: order === 'asc',
ExclusiveStartKey: startFromSk
? this.toDdbKey({ pk, sk: startFromSk })
: undefined,
KeyConditionExpression: 'pk = :pk' + (sk ? ` and ${sk.skExpression}` : ''),
ExpressionAttributeNames: sk?.skAttributeNames,
ExpressionAttributeValues: await this.objToDdbItem(tableName, {
...sk?.skAttributeValues,
':pk': pk,
}),
}));
const lastSk = res.LastEvaluatedKey
? (0, util_dynamodb_1.unmarshall)(res.LastEvaluatedKey).sk
: undefined;
const items = await Promise.all(res.Items?.map((item) => this.ddbItemToObj(item)));
return {
lastSk,
items,
};
}
async listAllItems(tableName, startKey, limit = 10) {
const res = await this.client.send(new client_dynamodb_1.ScanCommand({
TableName: tableName,
Limit: limit,
ExclusiveStartKey: startKey ? this.toDdbKey(startKey) : undefined,
}));
const lastKey = res.LastEvaluatedKey
? (0, util_dynamodb_1.unmarshall)(res.LastEvaluatedKey)
: undefined;
const items = await Promise.all(res.Items?.map((item) => this.ddbItemToObj(item)));
return {
lastKey,
items,
};
}
async ddbItemToObj(item) {
if (!item) {
return item;
}
const obj = (0, util_dynamodb_1.unmarshall)(item);
if ((0, helpers_1.isS3AttributeKey)(obj.attributes)) {
const { key } = (0, helpers_1.parseS3AttributeKey)(obj.attributes);
obj.attributes = await this.s3Service.getItem(key);
}
if (obj.createdAt) {
obj.createdAt = new Date(obj.createdAt);
}
if (obj.updatedAt) {
obj.updatedAt = new Date(obj.updatedAt);
}
return obj;
}
async objToDdbItem(tableName, obj) {
if (!obj) {
return obj;
}
const data = { ...obj };
for (const key in data) {
const value = data[key];
if (value instanceof Date) {
data[key] = (0, helpers_1.toISOStringWithTimezone)(value);
}
}
if (data.attributes || data[':attributes']) {
const attributes = data.attributes || data[':attributes'];
// check size of attributes
const bytes = (0, object_1.objectBytes)(data.attributes || data[':attributes']);
if (bytes > this.config.get('ATTRIBUTE_LIMIT_SIZE')) {
// save to s3
const { pk, sk } = data;
const key = `ddb/${tableName}/${pk}/${sk}/${(0, ulid_1.ulid)()}.json`;
const s3Key = await this.s3Service.putItem(key, attributes);
// assign s3 url to attributes
data.attributes = (0, helpers_1.toS3AttributeKey)(s3Key.Bucket, s3Key.Key);
}
}
return (0, util_dynamodb_1.marshall)(data, {
convertClassInstanceToMap: true,
removeUndefinedValues: true,
});
}
toDdbKey(key) {
return (0, util_dynamodb_1.marshall)(key, {
convertClassInstanceToMap: true,
removeUndefinedValues: true,
});
}
buildUpdateExpression(input) {
const ret = {
updateExpression: '',
expressionAttributeNames: {},
expressionAttributeValues: {},
};
const mapper = {
set: this.buildUpdateSetExpression.bind(this),
remove: this.buildUpdateRemoveExpression.bind(this),
delete: this.buildUpdateDeleteExpression.bind(this),
};
for (const key in mapper) {
if (!input[key]) {
continue;
}
const expr = mapper[key](input[key]);
ret.updateExpression += ' ' + expr.updateExpression;
ret.expressionAttributeNames = {
...ret.expressionAttributeNames,
...expr.expressionAttributeNames,
};
ret.expressionAttributeValues = {
...ret.expressionAttributeValues,
...expr.expressionAttributeValues,
};
}
return ret;
}
buildUpdateSetExpression(inputSet) {
const ret = {
updateExpression: '',
expressionAttributeNames: {},
expressionAttributeValues: {},
};
const updExprList = [];
for (const key in inputSet) {
const val = inputSet[key];
if (typeof val === 'undefined') {
continue;
}
let updExpr = `#${key}=:${key}`;
ret.expressionAttributeNames[`#${key}`] = key;
if (typeof val === 'object') {
let isSetValue = false;
// ifNotExists
if ('ifNotExists' in val) {
isSetValue = true;
const ifNotExists = val.ifNotExists;
const path = `#${ifNotExists.path || key}`;
const value = `:${key}InitVal`;
updExpr = `#${key}=if_not_exists(${path}, ${value})`;
ret.expressionAttributeValues[value] =
ifNotExists.value || ifNotExists;
if (!ret.expressionAttributeNames[path]) {
ret.expressionAttributeNames[path] = path.substring(1);
}
}
// incrementBy | decrementBy
if ('incrementBy' in val) {
isSetValue = true;
const value = `:${key}IncrementBy`;
updExpr += ` + ${value}`;
ret.expressionAttributeValues[value] = val.incrementBy;
}
if ('decrementBy' in val) {
isSetValue = true;
const value = `:${key}DecrementBy`;
updExpr += ` - ${value}`;
ret.expressionAttributeValues[value] = val.decrementBy;
}
// listAppend
if ('listAppend' in val) {
isSetValue = true;
const listAppend = val.listAppend;
const path = `#${listAppend.path || key}`;
const value = `:appendFor${path.substring(1)}`;
updExpr = `#${key}=list_append(${path}, ${value})`;
ret.expressionAttributeValues[value] = listAppend.value || listAppend;
if (!ret.expressionAttributeNames[path]) {
ret.expressionAttributeNames[path] = path.substring(1);
}
}
// set a map to key
if (!isSetValue) {
ret.expressionAttributeValues[`:${key}`] = val;
}
}
else {
ret.expressionAttributeValues[`:${key}`] = val;
}
updExprList.push(updExpr);
}
ret.updateExpression += `SET ${updExprList.join(', ')}`;
return ret;
}
buildUpdateRemoveExpression(inputRemove) {
const ret = {
updateExpression: '',
expressionAttributeNames: {},
expressionAttributeValues: {},
};
const updExprList = [];
for (const key in inputRemove) {
const val = inputRemove[key];
if (!val) {
continue;
}
let updExpr = `#${key}`;
if (typeof val === 'object' && 'index' in val) {
updExpr += `[${val.index}]`;
}
updExprList.push(updExpr);
ret.expressionAttributeNames[`#${key}`] = key;
}
ret.updateExpression += `REMOVE ${updExprList.join(', ')}`;
return ret;
}
buildUpdateDeleteExpression(inputDelete) {
const ret = {
updateExpression: '',
expressionAttributeNames: {},
expressionAttributeValues: {},
};
const updExprList = [];
for (const key in inputDelete) {
const val = inputDelete[key];
if (typeof val === 'undefined') {
continue;
}
updExprList.push(`#${key} :${key}`);
ret.expressionAttributeNames[`#${key}`] = key;
ret.expressionAttributeValues[`:${key}`] = val;
}
ret.updateExpression += `DELETE ${updExprList.join(', ')}`;
return ret;
}
getTableName(moduleName, type) {
let tableName = this.tablePrefix + moduleName;
if (type) {
tableName = `${tableName}-${type}`;
}
return tableName;
}
getModuleName(tableName) {
const removedPrefix = tableName.substring(this.tablePrefix.length);
return removedPrefix.substring(0, removedPrefix.lastIndexOf('-'));
}
};
exports.DynamoDbService = DynamoDbService;
exports.DynamoDbService = DynamoDbService = DynamoDbService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [config_1.ConfigService,
s3_service_1.S3Service])
], DynamoDbService);
//# sourceMappingURL=dynamodb.service.js.map