UNPKG

@mbc-cqrs-serverless/core

Version:
324 lines 13.2 kB
"use strict"; 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