dynabridge
Version:
Simple and light-weight TypeScript entity-focused wrapper for DynamoDB
225 lines (224 loc) • 7.8 kB
JavaScript
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
import { BatchGetItemCommand, DeleteItemCommand, GetItemCommand } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
export const getItem = async (ddbClient, tableName, key) => {
const command = new GetItemCommand({
TableName: tableName,
Key: marshall(key)
});
const result = await ddbClient.send(command);
if (result.Item) {
const persistedItem = unmarshall(result.Item);
const { _version, _updated_at, ...entity } = persistedItem;
return {
entity: entity,
version: _version ?? 1
};
}
return undefined;
};
export const putItem = async (ddbClient, tableName, version, entity) => {
const document = DynamoDBDocument.from(ddbClient, {
marshallOptions: {
removeUndefinedValues: true
}
});
const entityWithMetadata = {
...entity,
_version: version,
_updated_at: new Date().toISOString()
};
await document.put({
TableName: tableName,
Item: entityWithMetadata
});
};
export const getBatchItem = async (ddbClient, tableName, keys) => {
if (!keys || keys.length === 0) {
return [];
}
const idChunks = getChunks(keys);
const allItems = [];
for (const idChunk of idChunks) {
const chunkItems = [];
let chunkTry = 0;
let chunkRetrievedSuccessfully = false;
let itemsToGet = {
RequestItems: {
[tableName]: {
Keys: idChunk.map((id) => marshall(id))
}
}
};
while (!chunkRetrievedSuccessfully) {
if (chunkTry < 3) {
const res = await ddbClient.send(new BatchGetItemCommand(itemsToGet));
const potentialItems = res.Responses?.[tableName]?.map((item) => {
const persistedItem = unmarshall(item);
const { _version, _updated_at, ...entity } = persistedItem;
return {
entity: entity,
version: _version ?? 1
};
}) ?? [];
chunkItems.push(...potentialItems);
if (res.UnprocessedKeys && Object.keys(res.UnprocessedKeys).length > 0) {
itemsToGet = { RequestItems: res.UnprocessedKeys };
chunkTry = chunkTry + 1;
}
else {
allItems.push(...chunkItems);
chunkRetrievedSuccessfully = true;
}
}
else {
throw new Error('Failed after 3 retries getting chunk.');
}
}
}
if (!allItems || allItems.length === 0) {
return [];
}
return allItems;
};
export const writeBatchItem = async (ddbClient, tableName, version, entities) => {
if (entities.length === 0) {
return;
}
const entitiesWithMetadata = entities.map((entity) => ({
...entity,
_version: version,
_updated_at: new Date().toISOString()
}));
const translateConfig = {
marshallOptions: { removeUndefinedValues: true }
};
const dynamoDocClient = DynamoDBDocument.from(ddbClient, translateConfig);
const chunks = getChunks(entitiesWithMetadata);
for (const chunk of chunks) {
let chunkTry = 0;
let chunkWrittenSuccessfully = false;
let itemsToWrite = {
RequestItems: {
[tableName]: chunk.map((item) => ({
PutRequest: {
Item: item
}
}))
}
};
while (!chunkWrittenSuccessfully) {
if (chunkTry < 3) {
const res = await dynamoDocClient.batchWrite(itemsToWrite);
if (res.UnprocessedItems && Object.keys(res.UnprocessedItems).length > 0) {
itemsToWrite = { RequestItems: res.UnprocessedItems };
chunkTry = chunkTry + 1;
}
else {
chunkWrittenSuccessfully = true;
}
}
else {
throw new Error('Failed after 3 retries writing chunk.');
}
}
}
};
export const deleteBatchItem = async (ddbClient, tableName, keys) => {
if (keys.length === 0) {
return;
}
const dynamoDocClient = DynamoDBDocument.from(ddbClient);
const chunks = getChunks(keys);
for (const chunk of chunks) {
let chunkTry = 0;
let chunkDeletedSuccessfully = false;
let itemsToDelete = {
RequestItems: {
[tableName]: chunk.map((key) => ({
DeleteRequest: {
Key: key
}
}))
}
};
while (!chunkDeletedSuccessfully) {
if (chunkTry < 3) {
const res = await dynamoDocClient.batchWrite(itemsToDelete);
if (res.UnprocessedItems && Object.keys(res.UnprocessedItems).length > 0) {
itemsToDelete = { RequestItems: res.UnprocessedItems };
chunkTry = chunkTry + 1;
}
else {
chunkDeletedSuccessfully = true;
}
}
else {
throw new Error('Failed after 3 retries writing chunk.');
}
}
}
};
export async function query(ddbClient, queryParams) {
const documentClient = DynamoDBDocument.from(ddbClient);
let allEntities = [];
let lastEvaluatedKey;
const params = { ...queryParams };
do {
if (lastEvaluatedKey) {
params.ExclusiveStartKey = lastEvaluatedKey;
}
const result = await documentClient.query(params);
if (result.Items && result.Items.length > 0) {
const entities = result.Items.map((item) => {
const { _version, _updated_at, ...entity } = item;
return { entity: entity, version: _version ?? 1 };
});
allEntities = [...allEntities, ...entities];
}
lastEvaluatedKey = result.LastEvaluatedKey;
} while (lastEvaluatedKey);
return allEntities;
}
export const scan = async (ddbClient, tableName) => {
const scanParams = {
TableName: tableName,
ExclusiveStartKey: undefined
};
const documentClient = DynamoDBDocument.from(ddbClient);
const scanResults = [];
let items;
do {
items = await documentClient.scan(scanParams);
items.Items?.forEach((item) => {
const { _version, _updated_at, ...entity } = item;
scanResults.push({
entity: entity,
version: _version ?? 1
});
});
scanParams.ExclusiveStartKey = items.LastEvaluatedKey;
} while (typeof items.LastEvaluatedKey !== 'undefined');
return scanResults;
};
export const transactWrite = async (ddbClient, transactItems) => {
const document = DynamoDBDocument.from(ddbClient, {
marshallOptions: {
removeUndefinedValues: true
}
});
await document.transactWrite({
TransactItems: transactItems
});
};
export const deleteItem = async (ddbClient, tableName, key) => {
const command = new DeleteItemCommand({
TableName: tableName,
Key: marshall(key)
});
await ddbClient.send(command);
};
const getChunks = (items) => {
const CHUNK_SIZE = 25;
return [...Array(Math.ceil(items.length / CHUNK_SIZE)).keys()].map((index) => items.slice(CHUNK_SIZE * index, CHUNK_SIZE + CHUNK_SIZE * index));
};