@jzhuo3/dynamodb-lib 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PUBLISHING.md +9 -1
- package/dist/cjs/index.js +58 -102
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +58 -102
- package/dist/esm/index.js.map +1 -1
- package/doc/service.md +36 -0
- package/package.json +4 -3
package/dist/esm/index.js
CHANGED
|
@@ -388,6 +388,7 @@ export class DynamoDBTransaction {
|
|
|
388
388
|
if (i === retries - 1) {
|
|
389
389
|
throw error;
|
|
390
390
|
}
|
|
391
|
+
this.logger.warn('DynamoDB retrying transaction conflict', { mode: this.mode, attempt: i + 1, maxAttempts: retries, delayMs: delay * (i + 1) });
|
|
391
392
|
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
|
|
392
393
|
}
|
|
393
394
|
else {
|
|
@@ -636,10 +637,6 @@ export class DynamoDBTransaction {
|
|
|
636
637
|
}
|
|
637
638
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
638
639
|
});
|
|
639
|
-
this.logger.debug('Update Expression: %s', UpdateExpression);
|
|
640
|
-
this.logger.debug('Condition Expression: %s', ConditionExpression);
|
|
641
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
642
|
-
this.logger.debug('Expression Attribute Values: %o', ExpressionAttributeValues);
|
|
643
640
|
this.transactWriteItems.push({
|
|
644
641
|
Update: {
|
|
645
642
|
TableName: table.name,
|
|
@@ -715,6 +712,7 @@ export class DynamoDBTransaction {
|
|
|
715
712
|
const result = {
|
|
716
713
|
numberOfTransactItems: 0,
|
|
717
714
|
};
|
|
715
|
+
this.logger.debug('DynamoDB transaction started', { mode: this.mode, itemCount: this.mode === DynamoDBTransactionMode.READ ? this.transactGetItems.length : this.transactWriteItems.length });
|
|
718
716
|
switch (this.mode) {
|
|
719
717
|
case DynamoDBTransactionMode.READ:
|
|
720
718
|
command = new TransactGetCommand({
|
|
@@ -726,21 +724,21 @@ export class DynamoDBTransaction {
|
|
|
726
724
|
const { Responses } = (await this.txConflictRetryWrapper(command, this.txRetryLimit, this.txRetryDelay).catch((e) => {
|
|
727
725
|
this._cleanup();
|
|
728
726
|
if (isTransactionConflictError(e)) {
|
|
729
|
-
this.logger.error('
|
|
727
|
+
this.logger.error('DynamoDB transaction conflict retry limit reached', { errorName: e.name });
|
|
730
728
|
throw new DynamoDBError(409, {
|
|
731
729
|
message: e.message,
|
|
732
730
|
cause: e,
|
|
733
731
|
});
|
|
734
732
|
}
|
|
735
733
|
else if (e.name === 'TransactionCanceledException') {
|
|
736
|
-
this.logger.error(e.
|
|
734
|
+
this.logger.error('DynamoDB transaction canceled', { errorName: e.name });
|
|
737
735
|
throw new DynamoDBError(400, {
|
|
738
736
|
message: e.message,
|
|
739
737
|
cause: e,
|
|
740
738
|
});
|
|
741
739
|
}
|
|
742
740
|
else {
|
|
743
|
-
this.logger.error('Transaction failed
|
|
741
|
+
this.logger.error('Transaction failed', { errorName: e.name });
|
|
744
742
|
throw new DynamoDBError(500, {
|
|
745
743
|
message: e.message,
|
|
746
744
|
cause: e,
|
|
@@ -789,28 +787,28 @@ export class DynamoDBTransaction {
|
|
|
789
787
|
await this.txConflictRetryWrapper(command, this.txRetryLimit, this.txRetryDelay).catch((e) => {
|
|
790
788
|
this._cleanup();
|
|
791
789
|
if (isTransactionConflictError(e)) {
|
|
792
|
-
this.logger.error('
|
|
790
|
+
this.logger.error('DynamoDB transaction conflict retry limit reached', { errorName: e.name });
|
|
793
791
|
throw new DynamoDBError(409, {
|
|
794
792
|
message: e.message,
|
|
795
793
|
cause: e,
|
|
796
794
|
});
|
|
797
795
|
}
|
|
798
796
|
else if (e.name === 'TransactionCanceledException') {
|
|
799
|
-
this.logger.error(e.
|
|
797
|
+
this.logger.error('DynamoDB transaction canceled', { errorName: e.name });
|
|
800
798
|
throw new DynamoDBError(400, {
|
|
801
799
|
message: e.message,
|
|
802
800
|
cause: e,
|
|
803
801
|
});
|
|
804
802
|
}
|
|
805
803
|
else if (e.name === 'ValidationException') {
|
|
806
|
-
this.logger.error('The request parameters are invalid
|
|
804
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name });
|
|
807
805
|
throw new DynamoDBError(400, {
|
|
808
806
|
message: 'Invalid request parameters',
|
|
809
807
|
cause: e,
|
|
810
808
|
});
|
|
811
809
|
}
|
|
812
810
|
else {
|
|
813
|
-
this.logger.error('Transaction failed
|
|
811
|
+
this.logger.error('Transaction failed', { errorName: e.name });
|
|
814
812
|
throw new DynamoDBError(500, {
|
|
815
813
|
message: e.message,
|
|
816
814
|
cause: e,
|
|
@@ -823,6 +821,7 @@ export class DynamoDBTransaction {
|
|
|
823
821
|
default:
|
|
824
822
|
throw new Error('Invalid transaction mode');
|
|
825
823
|
}
|
|
824
|
+
this.logger.debug('DynamoDB transaction completed', { mode: this.mode, itemCount: result.numberOfTransactItems });
|
|
826
825
|
return result;
|
|
827
826
|
}
|
|
828
827
|
}
|
|
@@ -891,8 +890,7 @@ export default class DynamoDBService {
|
|
|
891
890
|
ExpressionAttributeNames[attributeNameVariable] = attributeName;
|
|
892
891
|
});
|
|
893
892
|
}
|
|
894
|
-
this.logger.debug('
|
|
895
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
893
|
+
this.logger.debug('DynamoDB request started', { operation: 'getOne', tableKey });
|
|
896
894
|
const getCommand = new GetCommand({
|
|
897
895
|
TableName: table.name,
|
|
898
896
|
Key: keys,
|
|
@@ -907,22 +905,14 @@ export default class DynamoDBService {
|
|
|
907
905
|
.send(getCommand)
|
|
908
906
|
.catch((e) => {
|
|
909
907
|
if (e.name === 'ValidationException') {
|
|
910
|
-
this.logger.error('The request parameters are invalid
|
|
911
|
-
tableKey,
|
|
912
|
-
primaryKey,
|
|
913
|
-
opts,
|
|
914
|
-
});
|
|
908
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name, tableKey });
|
|
915
909
|
throw new DynamoDBError(400, {
|
|
916
910
|
message: 'Invalid request parameters',
|
|
917
911
|
cause: e,
|
|
918
912
|
});
|
|
919
913
|
}
|
|
920
914
|
else {
|
|
921
|
-
this.logger.error('Failed to get item
|
|
922
|
-
tableKey,
|
|
923
|
-
primaryKey,
|
|
924
|
-
opts,
|
|
925
|
-
});
|
|
915
|
+
this.logger.error('Failed to get item', { errorName: e.name, tableKey });
|
|
926
916
|
throw new DynamoDBError(500, {
|
|
927
917
|
message: 'Failed to get item',
|
|
928
918
|
cause: e,
|
|
@@ -933,6 +923,7 @@ export default class DynamoDBService {
|
|
|
933
923
|
if (item && table.timeToLiveAttribute && item[table.timeToLiveAttribute] !== undefined) {
|
|
934
924
|
const now = getUnixTime(new Date());
|
|
935
925
|
if (item[table.timeToLiveAttribute] <= now) {
|
|
926
|
+
this.logger.debug('DynamoDB getOne completed', { tableKey, outcome: 'expired' });
|
|
936
927
|
return null;
|
|
937
928
|
}
|
|
938
929
|
// Delete the time to live attribute from the item if not requested
|
|
@@ -940,6 +931,7 @@ export default class DynamoDBService {
|
|
|
940
931
|
delete item[table.timeToLiveAttribute];
|
|
941
932
|
}
|
|
942
933
|
}
|
|
934
|
+
this.logger.debug('DynamoDB getOne completed', { tableKey, outcome: item ? 'found' : 'not-found' });
|
|
943
935
|
// Item will be undefined if not found
|
|
944
936
|
// Convert keys back to camelCase if camelOrSnake is 'camel' or to snake_case if camelOrSnake is 'snake'
|
|
945
937
|
if (item) {
|
|
@@ -973,7 +965,6 @@ export default class DynamoDBService {
|
|
|
973
965
|
const keys = primaryKeys.map((primaryKey, indx) => {
|
|
974
966
|
const isArrayOfKeys = Array.isArray(primaryKey);
|
|
975
967
|
const pkStr = serializeKey(primaryKey);
|
|
976
|
-
this.logger.debug('Primary Key: %s', pkStr);
|
|
977
968
|
if (pkIndexMap.has(pkStr)) {
|
|
978
969
|
throw new Error(`Duplicate primary key: ${pkStr}`);
|
|
979
970
|
}
|
|
@@ -1022,13 +1013,12 @@ export default class DynamoDBService {
|
|
|
1022
1013
|
ExpressionAttributeNames[attributeNameVariable] = attributeName;
|
|
1023
1014
|
});
|
|
1024
1015
|
}
|
|
1025
|
-
this.logger.debug('Projection Expression: %s', ProjectionExpression);
|
|
1026
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
1027
1016
|
const results = new Array(primaryKeys.length).fill(null);
|
|
1028
1017
|
const now = getUnixTime(new Date());
|
|
1029
1018
|
let unprocessedAttempt = 0;
|
|
1030
1019
|
while (keys.length > 0) {
|
|
1031
1020
|
const batch = keys.splice(0, 100);
|
|
1021
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'getMany', tableKey, itemCount: batch.length });
|
|
1032
1022
|
const batchGetCommand = new BatchGetCommand({
|
|
1033
1023
|
RequestItems: {
|
|
1034
1024
|
[table.name]: {
|
|
@@ -1042,19 +1032,23 @@ export default class DynamoDBService {
|
|
|
1042
1032
|
},
|
|
1043
1033
|
ReturnConsumedCapacity: opts.returnConsumedCapacity ?? ReturnConsumedCapacity.NONE,
|
|
1044
1034
|
});
|
|
1045
|
-
const { Responses: responses, UnprocessedKeys: rawUnprocessedKeys } = await this.ddbDocClient.send(batchGetCommand)
|
|
1035
|
+
const { Responses: responses, UnprocessedKeys: rawUnprocessedKeys } = await this.ddbDocClient.send(batchGetCommand).catch((error) => {
|
|
1036
|
+
this.logger.error('Failed to get items', { tableKey, errorName: error.name });
|
|
1037
|
+
throw error;
|
|
1038
|
+
});
|
|
1046
1039
|
const items = responses && responses[table.name] ? responses[table.name] : [];
|
|
1047
1040
|
const unprocessedKeys = (rawUnprocessedKeys && rawUnprocessedKeys[table.name]?.Keys) || [];
|
|
1048
|
-
this.logger.debug(`GetMany Responsed Items: %o`, items);
|
|
1049
|
-
this.logger.debug('Now: %d', now);
|
|
1050
1041
|
if (unprocessedKeys.length > 0) {
|
|
1051
1042
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
1043
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'getMany', tableKey, retryCount: unprocessedAttempt });
|
|
1052
1044
|
throw new DynamoDBError(500, {
|
|
1053
1045
|
message: 'Failed to retrieve items after multiple attempts',
|
|
1054
1046
|
cause: new Error('Too many unprocessed items'),
|
|
1055
1047
|
});
|
|
1056
1048
|
}
|
|
1057
|
-
|
|
1049
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
1050
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'getMany', tableKey, itemCount: unprocessedKeys.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
1051
|
+
await sleep(delayMs);
|
|
1058
1052
|
unprocessedAttempt += 1;
|
|
1059
1053
|
}
|
|
1060
1054
|
else {
|
|
@@ -1077,7 +1071,6 @@ export default class DynamoDBService {
|
|
|
1077
1071
|
const pkStr = serializeKey(table.sortKey
|
|
1078
1072
|
? [item[table.partitionKey], item[table.sortKey]]
|
|
1079
1073
|
: item[table.partitionKey]);
|
|
1080
|
-
this.logger.debug('Primary Key: %s', pkStr);
|
|
1081
1074
|
// Delete the primary key values from the item if not requested
|
|
1082
1075
|
if (optIncludePartitionKey === false) {
|
|
1083
1076
|
delete item[table.partitionKey];
|
|
@@ -1104,6 +1097,7 @@ export default class DynamoDBService {
|
|
|
1104
1097
|
}
|
|
1105
1098
|
});
|
|
1106
1099
|
}
|
|
1100
|
+
this.logger.debug('DynamoDB getMany completed', { tableKey, requestedCount: primaryKeys.length, returnedCount: results.filter(item => item !== null).length });
|
|
1107
1101
|
return results;
|
|
1108
1102
|
}
|
|
1109
1103
|
async find(tableKey, keyCondition, filter = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1266,7 +1260,6 @@ export default class DynamoDBService {
|
|
|
1266
1260
|
indexName: indexToScan.indexName,
|
|
1267
1261
|
projectionType: indexToScan
|
|
1268
1262
|
.projectionType,
|
|
1269
|
-
projectionFields,
|
|
1270
1263
|
});
|
|
1271
1264
|
}
|
|
1272
1265
|
break;
|
|
@@ -1296,12 +1289,11 @@ export default class DynamoDBService {
|
|
|
1296
1289
|
]).length > 0;
|
|
1297
1290
|
}
|
|
1298
1291
|
if (hasFieldNotInsideGSIProjection) {
|
|
1299
|
-
this.logger.error('Some of the projection fields are not included in the GSI projection,
|
|
1292
|
+
this.logger.error('Some of the projection fields are not included in the GSI projection, so the query cannot be executed', {
|
|
1300
1293
|
tableKey,
|
|
1301
1294
|
indexName: indexToScan.indexName,
|
|
1302
1295
|
projectionType: indexToScan
|
|
1303
1296
|
.projectionType,
|
|
1304
|
-
projectionFields,
|
|
1305
1297
|
});
|
|
1306
1298
|
throw new Error('All projection fields must be included in the GSI projection');
|
|
1307
1299
|
}
|
|
@@ -1358,11 +1350,6 @@ export default class DynamoDBService {
|
|
|
1358
1350
|
}
|
|
1359
1351
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
1360
1352
|
});
|
|
1361
|
-
this.logger.debug('Projection Expression: %s', ProjectionExpression);
|
|
1362
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
1363
|
-
this.logger.debug('Expression Attribute Values: %o', ExpressionAttributeValues);
|
|
1364
|
-
this.logger.debug('Key Condition Expression: %s', KeyConditionExpression);
|
|
1365
|
-
this.logger.debug('Filter Expression: %s', FilterExpression);
|
|
1366
1353
|
const numOfNeededItems = opts.limit; // May be undefined then no limit
|
|
1367
1354
|
const result = {
|
|
1368
1355
|
items: [],
|
|
@@ -1424,32 +1411,24 @@ export default class DynamoDBService {
|
|
|
1424
1411
|
: {}),
|
|
1425
1412
|
};
|
|
1426
1413
|
while (true) {
|
|
1414
|
+
this.logger.debug('DynamoDB query page requested', { tableKey, hasCursor: !!params.ExclusiveStartKey, hasFilter: !!params.FilterExpression });
|
|
1427
1415
|
const { Items, LastEvaluatedKey } = await this.ddbDocClient
|
|
1428
1416
|
.send(new QueryCommand(params))
|
|
1429
1417
|
.catch((e) => {
|
|
1430
1418
|
if (e.name === 'ValidationException') {
|
|
1431
|
-
this.logger.error('The request parameters are invalid
|
|
1432
|
-
tableKey,
|
|
1433
|
-
keyCondition,
|
|
1434
|
-
filter,
|
|
1435
|
-
opts,
|
|
1436
|
-
});
|
|
1419
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name, tableKey });
|
|
1437
1420
|
throw new DynamoDBError(400, {
|
|
1438
1421
|
message: 'Invalid request parameters',
|
|
1439
1422
|
cause: e,
|
|
1440
1423
|
});
|
|
1441
1424
|
}
|
|
1442
|
-
this.logger.error('Failed to query items
|
|
1443
|
-
tableKey,
|
|
1444
|
-
keyCondition,
|
|
1445
|
-
filter,
|
|
1446
|
-
opts,
|
|
1447
|
-
});
|
|
1425
|
+
this.logger.error('Failed to query items', { errorName: e.name, tableKey });
|
|
1448
1426
|
throw new DynamoDBError(500, {
|
|
1449
1427
|
message: 'Failed to query items',
|
|
1450
1428
|
cause: e,
|
|
1451
1429
|
});
|
|
1452
1430
|
});
|
|
1431
|
+
this.logger.debug('DynamoDB query page received', { tableKey, itemCount: Items?.length ?? 0, hasMore: !!LastEvaluatedKey });
|
|
1453
1432
|
if (LastEvaluatedKey) {
|
|
1454
1433
|
params.ExclusiveStartKey = LastEvaluatedKey;
|
|
1455
1434
|
}
|
|
@@ -1594,6 +1573,7 @@ export default class DynamoDBService {
|
|
|
1594
1573
|
ExpressionAttributeNames[key] = value;
|
|
1595
1574
|
});
|
|
1596
1575
|
}
|
|
1576
|
+
this.logger.debug('DynamoDB request started', { operation: 'createOne', tableKey });
|
|
1597
1577
|
const putCommand = new PutCommand({
|
|
1598
1578
|
TableName: table.name,
|
|
1599
1579
|
Item: normalizedItem,
|
|
@@ -1646,22 +1626,18 @@ export default class DynamoDBService {
|
|
|
1646
1626
|
const error = e;
|
|
1647
1627
|
if (error.name === 'ConditionalCheckFailedException' &&
|
|
1648
1628
|
opts.onConflict === 'ignore') {
|
|
1649
|
-
this.logger.debug('
|
|
1650
|
-
(table.sortKey ? `:${normalizedItem[table.sortKey]}` : ''));
|
|
1629
|
+
this.logger.debug('DynamoDB create skipped: item already exists', { tableKey });
|
|
1651
1630
|
result.created = false;
|
|
1652
1631
|
}
|
|
1653
1632
|
else {
|
|
1654
|
-
this.logger.error('Failed to create item
|
|
1655
|
-
tableKey,
|
|
1656
|
-
item: normalizedItem,
|
|
1657
|
-
opts,
|
|
1658
|
-
});
|
|
1633
|
+
this.logger.error('Failed to create item', { errorName: error.name, tableKey });
|
|
1659
1634
|
throw new DynamoDBError(500, {
|
|
1660
1635
|
message: 'Failed to create item',
|
|
1661
1636
|
cause: error,
|
|
1662
1637
|
});
|
|
1663
1638
|
}
|
|
1664
1639
|
}
|
|
1640
|
+
this.logger.debug('DynamoDB createOne completed', { tableKey, created: result.created });
|
|
1665
1641
|
return result;
|
|
1666
1642
|
}
|
|
1667
1643
|
async createMany(tableKey, primaryKeys, items, opts = {}) {
|
|
@@ -1706,6 +1682,7 @@ export default class DynamoDBService {
|
|
|
1706
1682
|
let unprocessedAttempt = 0;
|
|
1707
1683
|
while (normalizedItems.length > 0) {
|
|
1708
1684
|
const batch = normalizedItems.splice(0, 25);
|
|
1685
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'createMany', tableKey, itemCount: batch.length });
|
|
1709
1686
|
const batchWriteCommand = new BatchWriteCommand({
|
|
1710
1687
|
RequestItems: {
|
|
1711
1688
|
[table.name]: batch.map(item => ({
|
|
@@ -1719,11 +1696,7 @@ export default class DynamoDBService {
|
|
|
1719
1696
|
const { UnprocessedItems } = await this.ddbDocClient
|
|
1720
1697
|
.send(batchWriteCommand)
|
|
1721
1698
|
.catch((e) => {
|
|
1722
|
-
this.logger.error('Failed to create items
|
|
1723
|
-
tableKey,
|
|
1724
|
-
items: batch,
|
|
1725
|
-
opts,
|
|
1726
|
-
});
|
|
1699
|
+
this.logger.error('Failed to create items', { errorName: e.name, tableKey });
|
|
1727
1700
|
throw new DynamoDBError(500, {
|
|
1728
1701
|
message: 'Failed to create items',
|
|
1729
1702
|
cause: e,
|
|
@@ -1732,12 +1705,15 @@ export default class DynamoDBService {
|
|
|
1732
1705
|
const unprocessedItems = UnprocessedItems?.[table.name];
|
|
1733
1706
|
if (unprocessedItems !== undefined && unprocessedItems.length > 0) {
|
|
1734
1707
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
1708
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'createMany', tableKey, retryCount: unprocessedAttempt });
|
|
1735
1709
|
throw new DynamoDBError(500, {
|
|
1736
1710
|
message: 'Failed to create items after multiple attempts',
|
|
1737
1711
|
cause: new Error('Too many unprocessed items'),
|
|
1738
1712
|
});
|
|
1739
1713
|
}
|
|
1740
|
-
|
|
1714
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
1715
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'createMany', tableKey, itemCount: unprocessedItems.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
1716
|
+
await sleep(delayMs);
|
|
1741
1717
|
unprocessedAttempt += 1;
|
|
1742
1718
|
normalizedItems.push(...unprocessedItems
|
|
1743
1719
|
.map(item => item.PutRequest?.Item)
|
|
@@ -1747,6 +1723,7 @@ export default class DynamoDBService {
|
|
|
1747
1723
|
unprocessedAttempt = 0;
|
|
1748
1724
|
}
|
|
1749
1725
|
}
|
|
1726
|
+
this.logger.debug('DynamoDB createMany completed', { tableKey, itemCount: result.numberOfItems });
|
|
1750
1727
|
return result;
|
|
1751
1728
|
}
|
|
1752
1729
|
async update(tableKey, primaryKey, commands, condition = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1822,10 +1799,7 @@ export default class DynamoDBService {
|
|
|
1822
1799
|
}
|
|
1823
1800
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
1824
1801
|
});
|
|
1825
|
-
this.logger.debug('
|
|
1826
|
-
this.logger.debug('Condition Expression: %s', ConditionExpression);
|
|
1827
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
1828
|
-
this.logger.debug('Expression Attribute Values: %o', ExpressionAttributeValues);
|
|
1802
|
+
this.logger.debug('DynamoDB request started', { operation: 'update', tableKey });
|
|
1829
1803
|
const updateCommand = new UpdateCommand({
|
|
1830
1804
|
TableName: table.name,
|
|
1831
1805
|
Key: keys,
|
|
@@ -1859,29 +1833,17 @@ export default class DynamoDBService {
|
|
|
1859
1833
|
catch (e) {
|
|
1860
1834
|
const error = e;
|
|
1861
1835
|
if (error.name === 'ValidationException') {
|
|
1862
|
-
this.logger.error('The request parameters are invalid
|
|
1863
|
-
tableKey,
|
|
1864
|
-
primaryKey,
|
|
1865
|
-
commands,
|
|
1866
|
-
condition,
|
|
1867
|
-
opts,
|
|
1868
|
-
});
|
|
1836
|
+
this.logger.error('The request parameters are invalid', { errorName: error.name, tableKey });
|
|
1869
1837
|
throw new DynamoDBError(400, {
|
|
1870
1838
|
message: 'Invalid request parameters',
|
|
1871
1839
|
cause: e,
|
|
1872
1840
|
});
|
|
1873
1841
|
}
|
|
1874
1842
|
else if (error.name === 'ConditionalCheckFailedException') {
|
|
1875
|
-
this.logger.debug('
|
|
1843
|
+
this.logger.debug('DynamoDB update skipped: condition not met', { tableKey });
|
|
1876
1844
|
}
|
|
1877
1845
|
else {
|
|
1878
|
-
this.logger.error('Failed to update item
|
|
1879
|
-
tableKey,
|
|
1880
|
-
primaryKey,
|
|
1881
|
-
commands,
|
|
1882
|
-
condition,
|
|
1883
|
-
opts,
|
|
1884
|
-
});
|
|
1846
|
+
this.logger.error('Failed to update item', { errorName: error.name, tableKey });
|
|
1885
1847
|
throw new DynamoDBError(500, {
|
|
1886
1848
|
message: 'Failed to update item',
|
|
1887
1849
|
cause: e,
|
|
@@ -1907,6 +1869,7 @@ export default class DynamoDBService {
|
|
|
1907
1869
|
result.item = newItem;
|
|
1908
1870
|
}
|
|
1909
1871
|
}
|
|
1872
|
+
this.logger.debug('DynamoDB update completed', { tableKey, updatedOrCreated: result.updatedOrCreated });
|
|
1910
1873
|
return result;
|
|
1911
1874
|
}
|
|
1912
1875
|
async deleteOne(tableKey, primaryKey, condition = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1942,6 +1905,7 @@ export default class DynamoDBService {
|
|
|
1942
1905
|
}
|
|
1943
1906
|
ConditionExpression = condition.expression;
|
|
1944
1907
|
}
|
|
1908
|
+
this.logger.debug('DynamoDB request started', { operation: 'deleteOne', tableKey });
|
|
1945
1909
|
const deleteCommand = new DeleteCommand({
|
|
1946
1910
|
TableName: table.name,
|
|
1947
1911
|
Key: keys,
|
|
@@ -1969,27 +1933,17 @@ export default class DynamoDBService {
|
|
|
1969
1933
|
catch (e) {
|
|
1970
1934
|
const error = e;
|
|
1971
1935
|
if (error.name === 'ValidationException') {
|
|
1972
|
-
this.logger.error('The request parameters are invalid
|
|
1973
|
-
tableKey,
|
|
1974
|
-
primaryKey,
|
|
1975
|
-
condition,
|
|
1976
|
-
opts,
|
|
1977
|
-
});
|
|
1936
|
+
this.logger.error('The request parameters are invalid', { errorName: error.name, tableKey });
|
|
1978
1937
|
throw new DynamoDBError(400, {
|
|
1979
1938
|
message: 'Invalid request parameters',
|
|
1980
1939
|
cause: e,
|
|
1981
1940
|
});
|
|
1982
1941
|
}
|
|
1983
1942
|
else if (error.name === 'ConditionalCheckFailedException') {
|
|
1984
|
-
this.logger.debug('
|
|
1943
|
+
this.logger.debug('DynamoDB delete skipped: condition not met', { tableKey });
|
|
1985
1944
|
}
|
|
1986
1945
|
else {
|
|
1987
|
-
this.logger.error('Failed to delete item
|
|
1988
|
-
tableKey,
|
|
1989
|
-
primaryKey,
|
|
1990
|
-
condition,
|
|
1991
|
-
opts,
|
|
1992
|
-
});
|
|
1946
|
+
this.logger.error('Failed to delete item', { errorName: error.name, tableKey });
|
|
1993
1947
|
throw new DynamoDBError(500, {
|
|
1994
1948
|
message: 'Failed to delete item',
|
|
1995
1949
|
cause: e,
|
|
@@ -2015,6 +1969,7 @@ export default class DynamoDBService {
|
|
|
2015
1969
|
result.item = newItem;
|
|
2016
1970
|
}
|
|
2017
1971
|
}
|
|
1972
|
+
this.logger.debug('DynamoDB deleteOne completed', { tableKey, deleted: result.deleted });
|
|
2018
1973
|
return result;
|
|
2019
1974
|
}
|
|
2020
1975
|
async deleteMany(tableKey, primaryKeys, opts = {}) {
|
|
@@ -2039,6 +1994,7 @@ export default class DynamoDBService {
|
|
|
2039
1994
|
let unprocessedAttempt = 0;
|
|
2040
1995
|
while (keys.length > 0) {
|
|
2041
1996
|
const batch = keys.splice(0, 25);
|
|
1997
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'deleteMany', tableKey, itemCount: batch.length });
|
|
2042
1998
|
const batchWriteCommand = new BatchWriteCommand({
|
|
2043
1999
|
RequestItems: {
|
|
2044
2000
|
[table.name]: batch.map(key => ({
|
|
@@ -2052,11 +2008,7 @@ export default class DynamoDBService {
|
|
|
2052
2008
|
const { UnprocessedItems } = await this.ddbDocClient
|
|
2053
2009
|
.send(batchWriteCommand)
|
|
2054
2010
|
.catch((e) => {
|
|
2055
|
-
this.logger.error('Failed to delete items
|
|
2056
|
-
tableKey,
|
|
2057
|
-
keys: batch,
|
|
2058
|
-
opts,
|
|
2059
|
-
});
|
|
2011
|
+
this.logger.error('Failed to delete items', { errorName: e.name, tableKey });
|
|
2060
2012
|
throw new DynamoDBError(500, {
|
|
2061
2013
|
message: 'Failed to delete items',
|
|
2062
2014
|
cause: e,
|
|
@@ -2065,12 +2017,15 @@ export default class DynamoDBService {
|
|
|
2065
2017
|
const unprocessedItems = UnprocessedItems?.[table.name];
|
|
2066
2018
|
if (unprocessedItems !== undefined && unprocessedItems.length > 0) {
|
|
2067
2019
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
2020
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'deleteMany', tableKey, retryCount: unprocessedAttempt });
|
|
2068
2021
|
throw new DynamoDBError(500, {
|
|
2069
2022
|
message: 'Failed to delete items after multiple attempts',
|
|
2070
2023
|
cause: new Error('Too many unprocessed items'),
|
|
2071
2024
|
});
|
|
2072
2025
|
}
|
|
2073
|
-
|
|
2026
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
2027
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'deleteMany', tableKey, itemCount: unprocessedItems.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
2028
|
+
await sleep(delayMs);
|
|
2074
2029
|
unprocessedAttempt += 1;
|
|
2075
2030
|
keys.push(...unprocessedItems
|
|
2076
2031
|
.map(item => item.DeleteRequest?.Key)
|
|
@@ -2080,6 +2035,7 @@ export default class DynamoDBService {
|
|
|
2080
2035
|
unprocessedAttempt = 0;
|
|
2081
2036
|
}
|
|
2082
2037
|
}
|
|
2038
|
+
this.logger.debug('DynamoDB deleteMany completed', { tableKey, itemCount: result.numberOfItems });
|
|
2083
2039
|
return result;
|
|
2084
2040
|
}
|
|
2085
2041
|
transaction(opts) {
|