UNPKG

@baselinejs/dynamodb

Version:

DynamoDB library for simple and optimized way to use AWS DynamoDB

581 lines (578 loc) 20.6 kB
"use strict"; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts var src_exports = {}; __export(src_exports, { batchDeleteItems: () => batchDeleteItems, batchGetItems: () => batchGetItems, batchPutItems: () => batchPutItems, deleteItem: () => deleteItem, getAllItems: () => getAllItems, getDynamodbConnection: () => getDynamodbConnection, getItem: () => getItem, marshallItem: () => marshallItem, putItem: () => putItem, queryItems: () => queryItems, queryItemsRange: () => queryItemsRange, queryItemsRangeBetween: () => queryItemsRangeBetween, unmarshallItem: () => unmarshallItem, updateItem: () => updateItem }); module.exports = __toCommonJS(src_exports); var import_client_dynamodb = require("@aws-sdk/client-dynamodb"); var import_lib_dynamodb = require("@aws-sdk/lib-dynamodb"); var import_util_dynamodb = require("@aws-sdk/util-dynamodb"); // src/utils/utils.ts var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); var decorrelatedJitterBackoff = (previousDelay) => { const maxDelayMiliseconds = 4e3; const baseMiliseconds = 50; const nextDelay = Math.min( maxDelayMiliseconds, Math.random() * previousDelay * 3 + baseMiliseconds ); return nextDelay; }; var buildCondition = (args) => { const { operator, value, betweenSecondValue, field } = args; let conditionExpression = ""; switch (operator) { case "BeginsWith": conditionExpression = `begins_with(${field}, ${value})`; break; case "Equal": conditionExpression = `${field} = ${value}`; break; case "NotEqual": conditionExpression = `${field} <> ${value}`; break; case "GreaterThan": conditionExpression = `${field} > ${value}`; break; case "GreaterThanEqual": conditionExpression = `${field} >= ${value}`; break; case "LessThan": conditionExpression = `${field} < ${value}`; break; case "LessThanEqual": conditionExpression = `${field} <= ${value}`; break; case "Between": conditionExpression = `${field} BETWEEN ${value} AND ${betweenSecondValue}`; break; case "AttributeExists": conditionExpression = `attribute_exists(${field})`; break; case "AttributeNotExists": conditionExpression = `attribute_not_exists(${field})`; break; default: throw new Error("Unknown Query Condition type"); } return conditionExpression; }; var buildConditionExpression = (conditions) => { if (!(conditions == null ? void 0 : conditions.length)) { return null; } let count = 0; const attributeNames = {}; const attributeValues = {}; let conditionExpression = ""; conditions.forEach((values) => { if (conditionExpression == null ? void 0 : conditionExpression.length) { conditionExpression += " AND "; } conditionExpression += buildCondition({ field: `#field${count}`, value: `:val${count}`, operator: values.operator, betweenSecondValue: `:val${count + 1}` }); attributeNames[`#field${count}`] = values.field; if (values.value !== void 0) { attributeValues[`:val${count}`] = values.value; } if (values.betweenSecondValue !== void 0) { attributeValues[`:val${count + 1}`] = values.betweenSecondValue; } count += 2; }); if (!(conditionExpression == null ? void 0 : conditionExpression.length)) { return null; } return { conditionExpression, attributeNames: Object.keys(attributeNames).length ? attributeNames : null, attributeValues: Object.keys(attributeValues).length ? attributeValues : null }; }; // src/index.ts var IS_OFFLINE = process.env.IS_OFFLINE; var FORCE_ONLINE = process.env.FORCE_ONLINE; function getDynamodbConnection(config) { let newConnection; if (IS_OFFLINE === "true" && FORCE_ONLINE !== "true") { newConnection = import_lib_dynamodb.DynamoDBDocument.from( new import_client_dynamodb.DynamoDB({ region: "localhost", endpoint: "http://localhost:8000" }) ); } else { newConnection = import_lib_dynamodb.DynamoDBDocument.from(new import_client_dynamodb.DynamoDB(config)); } return newConnection; } var putItem = (params) => __async(void 0, null, function* () { const dynamoDb = params.dynamoDb; const putItemInput = { TableName: params.table, Item: params.item, ReturnValuesOnConditionCheckFailure: "ALL_OLD" }; const conditionalData = buildConditionExpression(params.conditions); if (conditionalData == null ? void 0 : conditionalData.attributeNames) { putItemInput.ExpressionAttributeNames = conditionalData.attributeNames; } if (conditionalData == null ? void 0 : conditionalData.attributeValues) { putItemInput.ExpressionAttributeValues = conditionalData.attributeValues; } if (conditionalData == null ? void 0 : conditionalData.conditionExpression) { putItemInput.ConditionExpression = conditionalData.conditionExpression; } yield dynamoDb.put(putItemInput); return params.item; }); var updateItem = (params) => __async(void 0, null, function* () { const updateItems = []; let count = 0; const keyNames = Object.keys(params.key); for (const element in params.fields) { const attributeValue = params.fields[element]; if (attributeValue !== void 0 && !keyNames.includes(element)) { updateItems.push({ name: element, attributeName: `#attr${count}`, attributeValue, ref: `:attr${count}` }); count++; } } const removeAttributeItems = []; for (const field of params.removeFields || []) { if (field !== void 0 && !keyNames.includes(field)) { removeAttributeItems.push({ name: field, attributeName: `#attr${count}` }); count++; } } if (!updateItems.length && !removeAttributeItems.length) { throw new Error("No fields or removeFields provided to updateItem"); } let updateExpression = ""; if (updateItems.length) { updateExpression = "SET " + updateItems.map((i) => `${i.attributeName}=${i.ref}`).join(", "); } if (removeAttributeItems.length) { updateExpression += (updateExpression.length > 1 ? " REMOVE " : "REMOVE") + removeAttributeItems.map((i) => i.attributeName).join(", "); } const expressionAttributeValues = updateItems.reduce( (p, c) => { p[`${c.ref}`] = c.attributeValue; return p; }, {} ); const expressionAttributeNames = [ ...updateItems, ...removeAttributeItems ].reduce( (p, c) => { p[`${c.attributeName}`] = c.name; return p; }, {} ); const updateItemInput = { TableName: params.table || "", Key: params.key, UpdateExpression: updateExpression, ReturnValues: "ALL_NEW", ExpressionAttributeNames: expressionAttributeNames, ExpressionAttributeValues: expressionAttributeValues, ReturnValuesOnConditionCheckFailure: "ALL_OLD" }; const conditionalData = buildConditionExpression(params.conditions); if (conditionalData == null ? void 0 : conditionalData.attributeNames) { updateItemInput.ExpressionAttributeNames = __spreadValues(__spreadValues({}, updateItemInput.ExpressionAttributeNames), conditionalData.attributeNames); } if (conditionalData == null ? void 0 : conditionalData.attributeValues) { updateItemInput.ExpressionAttributeValues = __spreadValues(__spreadValues({}, updateItemInput.ExpressionAttributeValues), conditionalData.attributeValues); } if (conditionalData == null ? void 0 : conditionalData.conditionExpression) { updateItemInput.ConditionExpression = conditionalData.conditionExpression; } const result = yield params.dynamoDb.update(updateItemInput); return result.Attributes; }); var deleteItem = (params) => __async(void 0, null, function* () { const deleteInput = { TableName: `${params.table}`, Key: params.key, ReturnValuesOnConditionCheckFailure: "ALL_OLD" }; const conditionalData = buildConditionExpression(params.conditions); if (conditionalData == null ? void 0 : conditionalData.attributeNames) { deleteInput.ExpressionAttributeNames = conditionalData.attributeNames; } if (conditionalData == null ? void 0 : conditionalData.attributeValues) { deleteInput.ExpressionAttributeValues = conditionalData.attributeValues; } if (conditionalData == null ? void 0 : conditionalData.conditionExpression) { deleteInput.ConditionExpression = conditionalData.conditionExpression; } yield params.dynamoDb.delete(deleteInput); return true; }); function getItem(params) { return __async(this, null, function* () { const getItemInput = { TableName: params.table || "", Key: params.key }; if (params.consistentRead) { getItemInput.ConsistentRead = params.consistentRead; } const result = yield params.dynamoDb.get(getItemInput); return result.Item; }); } function getAllItems(params) { return __async(this, null, function* () { const scanInput = { TableName: params.table || "" }; if (params.consistentRead) { scanInput.ConsistentRead = params.consistentRead; } if (params.limit) { scanInput.Limit = params.limit; } if (params.projectionExpression) { scanInput.ProjectionExpression = params.projectionExpression.join(","); } if (params.exclusiveStartKey) { scanInput.ExclusiveStartKey = params.exclusiveStartKey; } const conditionalData = buildConditionExpression(params.filterConditions); if (conditionalData == null ? void 0 : conditionalData.attributeNames) { scanInput.ExpressionAttributeNames = conditionalData.attributeNames; } if (conditionalData == null ? void 0 : conditionalData.attributeValues) { scanInput.ExpressionAttributeValues = conditionalData.attributeValues; } if (conditionalData == null ? void 0 : conditionalData.conditionExpression) { scanInput.FilterExpression = conditionalData.conditionExpression; } const allRecords = []; let lastKey = void 0; do { const result = yield params.dynamoDb.scan(scanInput); const resultRecords = result.Items; allRecords.push(...resultRecords); lastKey = result.LastEvaluatedKey; scanInput.ExclusiveStartKey = lastKey; } while (lastKey && (params.limit ? allRecords.length < params.limit : true)); return allRecords; }); } var batchGetItems = (params) => __async(void 0, null, function* () { var _a, _b, _c, _d, _e, _f; if (!params.keys.length) { return []; } const unique = /* @__PURE__ */ new Map(); for (const item of params.keys) { const key = JSON.stringify(item); if (!unique.has(key)) { unique.set(key, item); } } const uniqueIds = Array.from(unique.values()); const totalBatches = Math.ceil(uniqueIds.length / 100); const keyBatches = []; for (let index = 0; index < totalBatches; index++) { const start = index * 100; const end = start + 100 > uniqueIds.length ? uniqueIds.length : start + 100; const batch = uniqueIds.slice(start, end); keyBatches.push(batch); } const initialPromises = keyBatches.map((keyBatch) => { const batchGetInput = { RequestItems: { [params.table]: { Keys: keyBatch } } }; return params.dynamoDb.batchGet(batchGetInput); }); const initialResults = yield Promise.all(initialPromises); let allRecords = []; for (const result of initialResults) { const records = (_a = result.Responses) == null ? void 0 : _a[params.table]; allRecords = allRecords.concat(records); let unprocessedKeys = (_c = (_b = result.UnprocessedKeys) == null ? void 0 : _b[params.table]) == null ? void 0 : _c.Keys; while (unprocessedKeys && unprocessedKeys.length) { const batchGetInput = { RequestItems: { [params.table]: { Keys: unprocessedKeys } } }; const retryResult = yield params.dynamoDb.batchGet(batchGetInput); const retryRecords = (_d = retryResult.Responses) == null ? void 0 : _d[params.table]; allRecords = allRecords.concat(retryRecords); unprocessedKeys = (_f = (_e = retryResult.UnprocessedKeys) == null ? void 0 : _e[params.table]) == null ? void 0 : _f.Keys; } } return allRecords; }); function queryItems(params) { return __async(this, null, function* () { const queryInput = { TableName: params.table, KeyConditionExpression: `#a = :b`, ExpressionAttributeNames: { "#a": params.keyName }, ExpressionAttributeValues: { ":b": params.keyValue } }; if (params == null ? void 0 : params.indexName) { queryInput.IndexName = params == null ? void 0 : params.indexName; } if (params == null ? void 0 : params.exclusiveStartKey) { queryInput.ExclusiveStartKey = params == null ? void 0 : params.exclusiveStartKey; } if (params.consistentRead) { queryInput.ConsistentRead = params.consistentRead; } if ((params == null ? void 0 : params.scanIndexForward) === false) { queryInput.ScanIndexForward = false; } if (params == null ? void 0 : params.limit) { queryInput.Limit = params.limit; } if (params.projectionExpression) { queryInput.ProjectionExpression = params.projectionExpression.join(","); } const rangeData = buildConditionExpression( params.rangeCondition ? [params.rangeCondition] : void 0 ); if (rangeData == null ? void 0 : rangeData.attributeNames) { queryInput.ExpressionAttributeNames = __spreadValues(__spreadValues({}, queryInput.ExpressionAttributeNames), rangeData.attributeNames); } if (rangeData == null ? void 0 : rangeData.attributeValues) { queryInput.ExpressionAttributeValues = __spreadValues(__spreadValues({}, queryInput.ExpressionAttributeValues), rangeData.attributeValues); } if (rangeData == null ? void 0 : rangeData.conditionExpression) { queryInput.KeyConditionExpression = `${queryInput.KeyConditionExpression} AND ${rangeData == null ? void 0 : rangeData.conditionExpression}`; } const allRecords = []; let lastKey = void 0; do { const result = yield params.dynamoDb.query(queryInput); const resultRecords = result.Items; allRecords.push(...resultRecords); lastKey = result.LastEvaluatedKey; queryInput.ExclusiveStartKey = lastKey; } while (lastKey && (params.limit ? allRecords.length < params.limit : true)); return allRecords; }); } function queryItemsRange(params) { return __async(this, null, function* () { const result = yield queryItems(__spreadProps(__spreadValues({}, params), { rangeCondition: { operator: params.fuzzy ? "BeginsWith" : "Equal", field: params.rangeKeyName, value: params.rangeKeyValue } })); return result; }); } function queryItemsRangeBetween(params) { return __async(this, null, function* () { const result = yield queryItems(__spreadProps(__spreadValues({}, params), { rangeCondition: { operator: "Between", field: params.rangeKeyName, value: params.rangeKeyValueMin, betweenSecondValue: params.rangeKeyValueMax } })); return result; }); } var batchPutItems = (params) => __async(void 0, null, function* () { const totalBatches = Math.ceil(params.items.length / 25); const itemBatches = []; for (let index = 0; index < totalBatches; index++) { const start = index * 25; const end = start + 25 > params.items.length ? params.items.length : start + 25; const batch = params.items.slice(start, end); itemBatches.push(batch); } const initialPromises = itemBatches.map((itemsBatch) => { const batchWriteInput = { RequestItems: { [params.table]: itemsBatch.map((item) => ({ PutRequest: { Item: item } })) } }; return params.dynamoDb.batchWrite(batchWriteInput); }); const initialResults = yield Promise.all(initialPromises); let previousDelay = 0; for (const result of initialResults) { let unprocessedItems = result.UnprocessedItems; while (Object.keys(unprocessedItems || {}).length > 0) { const batchWriteInput = { RequestItems: unprocessedItems }; const delay = decorrelatedJitterBackoff(previousDelay); yield sleep(delay); const retryResult = yield params.dynamoDb.batchWrite(batchWriteInput); unprocessedItems = retryResult.UnprocessedItems; } } return params.items; }); var batchDeleteItems = (params) => __async(void 0, null, function* () { const totalBatches = Math.ceil(params.keys.length / 25); const keyBatches = []; for (let index = 0; index < totalBatches; index++) { const start = index * 25; const end = start + 25 > params.keys.length ? params.keys.length : start + 25; const batch = params.keys.slice(start, end); keyBatches.push(batch); } const initialPromises = keyBatches.map((keysBatch) => { const batchWriteInput = { RequestItems: { [params.table]: keysBatch.map((item) => ({ DeleteRequest: { Key: item } })) } }; return params.dynamoDb.batchWrite(batchWriteInput); }); const initialResults = yield Promise.all(initialPromises); let previousDelay = 0; for (const result of initialResults) { let unprocessedItems = result.UnprocessedItems; while (Object.keys(unprocessedItems || {}).length > 0) { const batchWriteInput = { RequestItems: unprocessedItems }; const delay = decorrelatedJitterBackoff(previousDelay); yield sleep(delay); const retryResult = yield params.dynamoDb.batchWrite(batchWriteInput); unprocessedItems = retryResult.UnprocessedItems; } } return true; }); var unmarshallItem = (item, options) => { const unmarshallItem2 = (0, import_util_dynamodb.unmarshall)(item, options); return unmarshallItem2; }; var marshallItem = (item, options) => { const unmarshallItem2 = (0, import_util_dynamodb.marshall)(item, options); return unmarshallItem2; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { batchDeleteItems, batchGetItems, batchPutItems, deleteItem, getAllItems, getDynamodbConnection, getItem, marshallItem, putItem, queryItems, queryItemsRange, queryItemsRangeBetween, unmarshallItem, updateItem }); //# sourceMappingURL=index.js.map