UNPKG

@nerdware/ddb-single-table

Version:

A schema-based DynamoDB modeling tool, high-level API, and type-generator built to supercharge single-table designs!⚡

251 lines (250 loc) 12.8 kB
import { // MODEL-METHOD COMMANDS GetItemCommand, BatchGetItemCommand, PutItemCommand, BatchWriteItemCommand, UpdateItemCommand, DeleteItemCommand, QueryCommand, ScanCommand, TransactWriteItemsCommand, // TABLE-METHOD COMMANDS DescribeTableCommand, CreateTableCommand, ListTablesCommand, } from "@aws-sdk/client-dynamodb"; import { isArray } from "@nerdware/ts-type-safety-utils"; import { DdbClientFieldParser } from "./DdbClientFieldParser.js"; import { handleBatchRequests, MAX_CHUNK_SIZE } from "./handleBatchRequests.js"; /** * A DynamoDB client wrapper with methods for instantiating DynamoDB client commands. * Where applicable, these wrapper methods also handle the following concerns: * * - Marshalling and unmarshalling of attribute values * - Conversion of JS `Date` objects to/from ISO-8601 datetime strings * - Batching/retry logic */ export class DdbClientWrapper extends DdbClientFieldParser { /** The DynamoDB client instance. */ _ddbClient; constructor({ ddbClient, tableName, marshallingConfigs }) { super({ tableName, marshallingConfigs }); this._ddbClient = ddbClient; } /** * [`GetItem`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html */ getItem = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, ...args }) => { // Create a GetItemCommand with marshalled `Key` const cmd = new GetItemCommand(this.prepCommandArgs(args, marshallOptions)); // Send the command to the DynamoDB client const response = await this._ddbClient.send(cmd); // Return response with unmarshalled `Item` return this.parseClientResponse(response, unmarshallOptions); }; /** * [`BatchGetItem`][api-ref] command wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html */ batchGetItems = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, batchConfigs = {}, ...rawCmdArgs }) => { // Init variables to hold returned values: const returnedItems = []; const returnedConsumedCapacity = []; let returnedMetadata = {}; // Create BatchGetItemCommand args with marshalled `RequestItems` const { RequestItems, ...cmdArgs } = this.prepCommandArgs(rawCmdArgs, marshallOptions); // Destructure the `KeysAndAttributes` object: const { Keys: marshalledRequestItemsKeys, ...nonKeyReqParams } = RequestItems[this.tableName]; // Define the fn for the batch-requests handler, ensure it updates `returnedItems` const submitBatchGetItemRequest = async (batchGetItemReqObjects) => { const response = await this._ddbClient.send(new BatchGetItemCommand({ ...cmdArgs, RequestItems: { [this.tableName]: { ...nonKeyReqParams, Keys: batchGetItemReqObjects, }, }, })); // Destructure relevant fields from the `response` const { Responses, ConsumedCapacity, UnprocessedKeys, $metadata } = response; // If the response returned items, add them to the `returnedItems` array if (isArray(Responses?.[this.tableName])) returnedItems.push(...Responses[this.tableName]); // Handle ConsumedCapacity if (isArray(ConsumedCapacity)) returnedConsumedCapacity.push(...ConsumedCapacity); // Handle $metadata returnedMetadata = $metadata; // Return any unprocessed keys return UnprocessedKeys?.[this.tableName]?.Keys; }; // Submit the function to the batch-requests handler const unprocessedKeys = await handleBatchRequests(submitBatchGetItemRequest, marshalledRequestItemsKeys, { chunkSize: MAX_CHUNK_SIZE.GetRequest, ...batchConfigs, }); return this.parseClientResponse({ ...(returnedItems.length > 0 && { Responses: { [this.tableName]: returnedItems }, }), ...(unprocessedKeys && { UnprocessedKeys: { [this.tableName]: { Keys: unprocessedKeys } }, }), ...(returnedConsumedCapacity.length > 0 && { ConsumedCapacity: returnedConsumedCapacity, }), $metadata: returnedMetadata, }, unmarshallOptions); }; /** * [`PutItem`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html */ putItem = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, ...args }) => { // Create a PutItemCommand with marshalled `Item` and `ExpressionAttributeValues` const cmd = new PutItemCommand(this.prepCommandArgs(args, marshallOptions)); // Send the command to the DynamoDB client const response = await this._ddbClient.send(cmd); // Return response with unmarshalled `Attributes` and `ItemCollectionMetrics.ItemCollectionKey` return this.parseClientResponse(response, unmarshallOptions); }; /** * [`UpdateItem`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html */ updateItem = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, ...args }) => { // Create an UpdateItemCommand with marshalled `Key` and `ExpressionAttributeValues` const cmd = new UpdateItemCommand(this.prepCommandArgs(args, marshallOptions)); // Send the command to the DynamoDB client const response = await this._ddbClient.send(cmd); // Return response with unmarshalled `Attributes` and `ItemCollectionMetrics.ItemCollectionKey` return this.parseClientResponse(response, unmarshallOptions); }; /** * [`DeleteItem`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html */ deleteItem = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, ...args }) => { // Create a DeleteItemCommand with marshalled `Key` and `ExpressionAttributeValues` const cmd = new DeleteItemCommand(this.prepCommandArgs(args, marshallOptions)); // Send the command to the DynamoDB client const response = await this._ddbClient.send(cmd); // Return response with unmarshalled `Attributes` and `ItemCollectionMetrics.ItemCollectionKey` return this.parseClientResponse(response, unmarshallOptions); }; /** * [`BatchWriteItem`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html */ batchWriteItems = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, batchConfigs = {}, ...rawCmdArgs }) => { // Init variables to hold returned values: const returnedItemCollectionMetrics = []; const returnedConsumedCapacity = []; let returnedMetadata = {}; // Create BatchWriteItemCommand args with marshalled `RequestItems` const { RequestItems, ...cmdArgs } = this.prepCommandArgs(rawCmdArgs, marshallOptions); // Get the marshalled `RequestItems` const marshalledRequestItems = RequestItems[this.tableName]; // Define the fn for the batch-requests handler, ensure it updates `returnedItems` const submitBatchWriteItemRequest = async (batchWriteItemReqObjects) => { const response = await this._ddbClient.send(new BatchWriteItemCommand({ ...cmdArgs, RequestItems: { [this.tableName]: batchWriteItemReqObjects, }, })); // Destructure relevant fields from the `response` const { ItemCollectionMetrics, ConsumedCapacity, UnprocessedItems, $metadata } = response; // If the response returned ItemCollectionMetrics, add them to the returned array if (isArray(ItemCollectionMetrics?.[this.tableName])) returnedItemCollectionMetrics.push(...ItemCollectionMetrics[this.tableName]); // Handle ConsumedCapacity if (isArray(ConsumedCapacity)) returnedConsumedCapacity.push(...ConsumedCapacity); // Handle $metadata returnedMetadata = $metadata; // Return any unprocessed items return UnprocessedItems?.[this.tableName]; }; // Submit the function to the batch-requests handler const unprocessedItems = await handleBatchRequests(submitBatchWriteItemRequest, marshalledRequestItems, // { chunkSize: MAX_CHUNK_SIZE.WriteRequest, ...batchConfigs, }); return this.parseClientResponse({ ...(unprocessedItems && { UnprocessedItems: { [this.tableName]: unprocessedItems }, }), ...(returnedConsumedCapacity.length > 0 && { ConsumedCapacity: returnedConsumedCapacity, }), ...(returnedItemCollectionMetrics.length > 0 && { ItemCollectionMetrics: { [this.tableName]: returnedItemCollectionMetrics }, }), $metadata: returnedMetadata, }, unmarshallOptions); }; /** * [`Query`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html */ query = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, ...args } = {}) => { // Create a QueryCommand with marshalled `ExclusiveStartKey` and `ExpressionAttributeValues` const cmd = new QueryCommand(this.prepCommandArgs(args, marshallOptions)); // Send the command to the DynamoDB client const response = await this._ddbClient.send(cmd); // Return response with unmarshalled `Items` and `LastEvaluatedKey` return this.parseClientResponse(response, unmarshallOptions); }; /** * [`Scan`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html */ scan = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, ...args } = {}) => { // Create a ScanCommand with marshalled `ExclusiveStartKey` and `ExpressionAttributeValues` const cmd = new ScanCommand(this.prepCommandArgs(args, marshallOptions)); // Send the command to the DynamoDB client const response = await this._ddbClient.send(cmd); // Return response with unmarshalled `Items` and `LastEvaluatedKey` return this.parseClientResponse(response, unmarshallOptions); }; /** * [`TransactWriteItems`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html */ transactWriteItems = async ({ marshallingConfigs: { marshallOptions, unmarshallOptions } = {}, ...args }) => { // Create the TransactWriteItemsCommand command with marshalled `TransactItems` const cmd = new TransactWriteItemsCommand(this.prepCommandArgs(args, marshallOptions)); // Send the command to the DynamoDB client const response = await this._ddbClient.send(cmd); // Return response with unmarshalled `ItemCollectionMetrics` return this.parseClientResponse(response, unmarshallOptions); }; // CONTROL PLANE METHODS: /** * [`DescribeTable`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html */ describeTable = async (args = {}) => { return await this._ddbClient.send(new DescribeTableCommand({ TableName: this.tableName, ...args })); }; /** * [`CreateTable`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html */ createTable = async (args) => { return await this._ddbClient.send(new CreateTableCommand({ TableName: this.tableName, ...args })); }; /** * [`ListTables`][api-ref] operation wrapper. * * [api-ref]: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTables.html */ listTables = async (args = {}) => { return await this._ddbClient.send(new ListTablesCommand(args)); }; }