@nerdware/ddb-single-table
Version:
A schema-based DynamoDB modeling tool, high-level API, and type-generator built to supercharge single-table designs!⚡
244 lines (243 loc) • 12.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DdbClientFieldParser = void 0;
const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
const ts_type_safety_utils_1 = require("@nerdware/ts-type-safety-utils");
const errors_js_1 = require("../utils/errors.js");
const convertJsDates_js_1 = require("./convertJsDates.js");
/**
* Utility class for preparing DynamoDB client command arguments and parsing client responses.
*/
class DdbClientFieldParser {
/**
* Default {@link MarshallingConfigs} for the DynamoDB `marshall` and `unmarshall` util fns.
* > Note: the SDK defaults all of these options to `false`.
*/
static DEFAULT_MARSHALLING_CONFIGS = {
marshallOptions: {
convertEmptyValues: false,
removeUndefinedValues: true,
convertClassInstanceToMap: true,
convertTopLevelContainer: false,
allowImpreciseNumbers: false,
},
unmarshallOptions: {
wrapNumbers: false,
convertWithoutMapWrapper: false,
},
};
tableName;
defaultMarshallingConfigs;
constructor({ tableName, marshallingConfigs }) {
this.tableName = tableName;
this.defaultMarshallingConfigs = {
marshallOptions: {
...DdbClientFieldParser.DEFAULT_MARSHALLING_CONFIGS.marshallOptions,
...marshallingConfigs?.marshallOptions,
},
unmarshallOptions: {
...DdbClientFieldParser.DEFAULT_MARSHALLING_CONFIGS.unmarshallOptions,
...marshallingConfigs?.unmarshallOptions,
},
};
}
/////////////////////////////////////////////////////////////////////////////
// MARSHALLING UTIL METHODS:
/** Invokes the `marshall` util function with the default marshalling configs. */
marshall = (data, marshallOpts) => {
return (0, util_dynamodb_1.marshall)(data, {
...this.defaultMarshallingConfigs.marshallOptions,
...marshallOpts,
});
};
/** Converts JS `Date` objects to datetime strings, and then marshalls the result. */
marshallAndConvertDates = (data, marshallOpts) => {
return this.marshall((0, convertJsDates_js_1.convertJsDates)("toDB", data), marshallOpts);
};
/** Invokes the `unmarshall` util function with the default unmarshalling configs. */
unmarshall = (data, unmarshallOpts) => {
return (0, util_dynamodb_1.unmarshall)(data, {
...this.defaultMarshallingConfigs.unmarshallOptions,
...unmarshallOpts,
});
};
/** Unmarshalls the provided `data`, and then converts datetime strings to JS `Date` objects. */
unmarshallAndConvertDates = (data, unmarshallOpts) => {
return (0, convertJsDates_js_1.convertJsDates)("fromDB", this.unmarshall(data, unmarshallOpts));
};
/////////////////////////////////////////////////////////////////////////////
// COMMAND-ARG PREP METHODS:
isBatchGetItemRequestItems = (requestItems) => {
return !(0, ts_type_safety_utils_1.isArray)(requestItems[this.tableName]);
};
prepRequestItems = (requestItems, marshallOptions) => {
return {
[this.tableName]: this.isBatchGetItemRequestItems(requestItems)
? {
...requestItems[this.tableName],
Keys: requestItems[this.tableName].Keys.map((key) => this.marshall(key, marshallOptions)),
}
: requestItems[this.tableName].map((batchWriteReq) => {
const { PutRequest, DeleteRequest } = batchWriteReq;
if (PutRequest)
return {
PutRequest: {
Item: this.marshallAndConvertDates(PutRequest.Item, marshallOptions),
},
};
if (DeleteRequest)
return {
DeleteRequest: {
Key: this.marshall(DeleteRequest.Key, marshallOptions),
},
};
throw new errors_js_1.ItemInputError(`Invalid request item for BatchWriteItem operation.
Expected a valid PutRequest or DeleteRequest.
Received: ${(0, ts_type_safety_utils_1.safeJsonStringify)(batchWriteReq)}`);
}),
};
};
/**
* This method prepares command arguments for DynamoDB client commands:
* - Adds the `TableName` parameter
* - Marshalls attribute values (`Key`, `Item`, `ExpressionAttributeValues`, etc.)
* - Converts JS Date objects to ISO-8601 datetime strings
*/
prepCommandArgs = (unmarshalledCommandArgs, marshallOpts) => {
// Destructure fields that require marshalling:
const { Key: key, Item: item, ExpressionAttributeValues: eav, ExclusiveStartKey: exclStartKey, RequestItems: requestItems, TransactItems: transactItems, ...otherCommandArgs } = unmarshalledCommandArgs;
return {
// Always add `TableName` UNLESS it's a batch or transaction command:
...(!requestItems && !transactItems && { TableName: this.tableName }),
...(key && { Key: this.marshall(key, marshallOpts) }),
...(item && { Item: this.marshallAndConvertDates(item, marshallOpts) }),
...(eav && { ExpressionAttributeValues: this.marshallAndConvertDates(eav, marshallOpts) }),
...(exclStartKey && { ExclusiveStartKey: this.marshall(exclStartKey, marshallOpts) }),
...(requestItems && { RequestItems: this.prepRequestItems(requestItems, marshallOpts) }),
...(transactItems && {
TransactItems: transactItems.map(
// Destructure the transactWriteItem to get the relevant fields
({ ConditionCheck: { Key: ccKey, ExpressionAttributeValues: ccEAV, ...ccRest } = {}, Put: { Item: putItem, ExpressionAttributeValues: putEAV, ...putRest } = {}, Update: { Key: updateKey, ExpressionAttributeValues: updEAV, ...updateRest } = {}, Delete: { Key: deleteKey, ExpressionAttributeValues: delEAV, ...delRest } = {}, }) => ({
...(ccKey && {
ConditionCheck: {
...ccRest,
TableName: this.tableName,
Key: this.marshall(ccKey, marshallOpts),
...(ccEAV && {
ExpressionAttributeValues: this.marshallAndConvertDates(ccEAV, marshallOpts),
}),
},
}),
...(putItem && {
Put: {
...putRest,
TableName: this.tableName,
Item: this.marshallAndConvertDates(putItem, marshallOpts),
...(putEAV && {
ExpressionAttributeValues: this.marshallAndConvertDates(putEAV, marshallOpts),
}),
},
}),
...(updateKey && {
Update: {
...updateRest,
TableName: this.tableName,
Key: this.marshall(updateKey, marshallOpts),
...(updEAV && {
ExpressionAttributeValues: this.marshallAndConvertDates(updEAV, marshallOpts),
}),
},
}),
...(deleteKey && {
Delete: {
...delRest,
TableName: this.tableName,
Key: this.marshall(deleteKey, marshallOpts),
...(delEAV && {
ExpressionAttributeValues: this.marshallAndConvertDates(delEAV, marshallOpts),
}),
},
}),
})),
}),
...otherCommandArgs,
};
};
/////////////////////////////////////////////////////////////////////////////
// RESPONSE-PARSING METHODS:
isSingleItemCollectionMetrics = (icMetrics) => {
return !!icMetrics.ItemCollectionKey || !!icMetrics.SizeEstimateRangeGB;
};
parseSingleItemCollectionMetrics = ({ ItemCollectionKey, SizeEstimateRangeGB }, unmarshallOptions) => ({
...(SizeEstimateRangeGB && {
SizeEstimateRangeGB,
}),
...(ItemCollectionKey && {
ItemCollectionKey: this.unmarshall(ItemCollectionKey, unmarshallOptions),
}),
});
parseItemCollectionMetrics = (icMetrics, unmarshallOptions) => {
return this.isSingleItemCollectionMetrics(icMetrics)
? this.parseSingleItemCollectionMetrics(icMetrics, unmarshallOptions)
: {
[this.tableName]: icMetrics[this.tableName].map((icm) => this.parseSingleItemCollectionMetrics(icm, unmarshallOptions)),
};
};
/**
* This method parses dynamodb-client reponses:
* - Unmarshalls attribute values (`Item`, `Items`, `Attributes`, `ItemCollectionKey`, etc.)
* - Converts ISO-8601 datetime strings to JS Date objects
*/
parseClientResponse = (clientResponse, unmarshallOptions) => {
// Destructure fields that require unmarshalling:
const { Item: item, Items: items, Attributes: attributes, LastEvaluatedKey: lastEvalKey, ItemCollectionMetrics: icMetrics, Responses: responses, UnprocessedKeys: unprocessedKeys, UnprocessedItems: unprocessedItems, ...otherFields } = clientResponse;
return {
...(item && {
Item: this.unmarshallAndConvertDates(item, unmarshallOptions),
}),
...(items && {
Items: items.map((item) => this.unmarshallAndConvertDates(item, unmarshallOptions)),
}),
...(attributes && {
Attributes: this.unmarshallAndConvertDates(attributes, unmarshallOptions),
}),
...(lastEvalKey && {
LastEvaluatedKey: this.unmarshall(lastEvalKey, unmarshallOptions),
}),
...(icMetrics && {
ItemCollectionMetrics: this.parseItemCollectionMetrics(icMetrics),
}),
...(responses && {
Responses: {
[this.tableName]: responses[this.tableName].map((item) => this.unmarshallAndConvertDates(item, unmarshallOptions)),
},
}),
...((0, ts_type_safety_utils_1.isArray)(unprocessedKeys?.[this.tableName]?.Keys) && {
UnprocessedKeys: {
[this.tableName]: {
...unprocessedKeys[this.tableName],
Keys: unprocessedKeys[this.tableName].Keys.map((key) => this.unmarshall(key, unmarshallOptions)),
},
},
}),
...((0, ts_type_safety_utils_1.isArray)(unprocessedItems?.[this.tableName]) && {
UnprocessedItems: {
[this.tableName]: unprocessedItems[this.tableName].map(({ PutRequest, DeleteRequest }) => ({
...(PutRequest && {
PutRequest: {
Item: this.unmarshallAndConvertDates(PutRequest.Item ?? {}, unmarshallOptions),
},
}),
...(DeleteRequest && {
DeleteRequest: {
Key: this.unmarshall(DeleteRequest.Key ?? {}, unmarshallOptions),
},
}),
})),
},
}),
...otherFields,
};
};
}
exports.DdbClientFieldParser = DdbClientFieldParser;