UNPKG

dynabridge

Version:

Simple and light-weight TypeScript entity-focused wrapper for DynamoDB

225 lines (224 loc) 7.8 kB
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)); };