@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/PUBLISHING.md
CHANGED
|
@@ -34,7 +34,15 @@ npm publish --access public
|
|
|
34
34
|
|
|
35
35
|
`prepublishOnly` runs the source, unit, build and package checks. Integration tests are explicit because they require an available database; CI runs them against DynamoDB Local. The AWS SDK dependency tree is deliberately bundled with the archive, alongside `npm-shrinkwrap.json`, to preserve the tested Node 18-compatible, patched dependencies. After updating dependencies, refresh the shrinkwrap and run the clean consumer installation check as well as the Node runtime matrix.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
## Automated publishing
|
|
38
|
+
|
|
39
|
+
Commit and push the workflows and both Release Please configuration files before creating the next release. `release-please.yml` uses the repository variable `RELEASE_APP_CLIENT_ID` and secret `RELEASE_APP_PRIVATE_KEY` to prepare release PRs and create GitHub releases after those PRs are merged.
|
|
40
|
+
|
|
41
|
+
When a non-prerelease GitHub release is published, `.github/workflows/publish.yml` checks that its tag is `vX.Y.Z` and matches `package.json`. It runs the reusable CI workflow against that exact commit, including unit, package, coverage, clean installation, and DynamoDB Local integration checks across Node 18, 20, 22, and 24. Only after verification passes does its `publish` job enter the `npm` environment and publish using Node 24 and npm 11. Draft releases and prereleases do not publish to npm.
|
|
42
|
+
|
|
43
|
+
Configure the npm package's GitHub Actions trusted publisher with owner `maczhuo`, repository `dynamodb-lib`, workflow filename `publish.yml`, environment `npm`, and permission for direct `npm publish`. The GitHub environment `npm` must allow release tags such as `v*`. No `NPM_TOKEN` is required. Any environment approval rules will pause the publish job until satisfied.
|
|
44
|
+
|
|
45
|
+
The workflow does not retroactively publish existing GitHub releases. If a run fails before publication, fix the external configuration and rerun the failed jobs from GitHub Actions. An already published npm version cannot be published again; code or workflow fixes require a new release containing those changes. Initial package creation may still require the manual bootstrap described above.
|
|
38
46
|
|
|
39
47
|
Official references (checked September 2026):
|
|
40
48
|
|
package/dist/cjs/index.js
CHANGED
|
@@ -421,6 +421,7 @@ class DynamoDBTransaction {
|
|
|
421
421
|
if (i === retries - 1) {
|
|
422
422
|
throw error;
|
|
423
423
|
}
|
|
424
|
+
this.logger.warn('DynamoDB retrying transaction conflict', { mode: this.mode, attempt: i + 1, maxAttempts: retries, delayMs: delay * (i + 1) });
|
|
424
425
|
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
|
|
425
426
|
}
|
|
426
427
|
else {
|
|
@@ -669,10 +670,6 @@ class DynamoDBTransaction {
|
|
|
669
670
|
}
|
|
670
671
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
671
672
|
});
|
|
672
|
-
this.logger.debug('Update Expression: %s', UpdateExpression);
|
|
673
|
-
this.logger.debug('Condition Expression: %s', ConditionExpression);
|
|
674
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
675
|
-
this.logger.debug('Expression Attribute Values: %o', ExpressionAttributeValues);
|
|
676
673
|
this.transactWriteItems.push({
|
|
677
674
|
Update: {
|
|
678
675
|
TableName: table.name,
|
|
@@ -748,6 +745,7 @@ class DynamoDBTransaction {
|
|
|
748
745
|
const result = {
|
|
749
746
|
numberOfTransactItems: 0,
|
|
750
747
|
};
|
|
748
|
+
this.logger.debug('DynamoDB transaction started', { mode: this.mode, itemCount: this.mode === DynamoDBTransactionMode.READ ? this.transactGetItems.length : this.transactWriteItems.length });
|
|
751
749
|
switch (this.mode) {
|
|
752
750
|
case DynamoDBTransactionMode.READ:
|
|
753
751
|
command = new lib_dynamodb_1.TransactGetCommand({
|
|
@@ -759,21 +757,21 @@ class DynamoDBTransaction {
|
|
|
759
757
|
const { Responses } = (await this.txConflictRetryWrapper(command, this.txRetryLimit, this.txRetryDelay).catch((e) => {
|
|
760
758
|
this._cleanup();
|
|
761
759
|
if (isTransactionConflictError(e)) {
|
|
762
|
-
this.logger.error('
|
|
760
|
+
this.logger.error('DynamoDB transaction conflict retry limit reached', { errorName: e.name });
|
|
763
761
|
throw new DynamoDBError(409, {
|
|
764
762
|
message: e.message,
|
|
765
763
|
cause: e,
|
|
766
764
|
});
|
|
767
765
|
}
|
|
768
766
|
else if (e.name === 'TransactionCanceledException') {
|
|
769
|
-
this.logger.error(e.
|
|
767
|
+
this.logger.error('DynamoDB transaction canceled', { errorName: e.name });
|
|
770
768
|
throw new DynamoDBError(400, {
|
|
771
769
|
message: e.message,
|
|
772
770
|
cause: e,
|
|
773
771
|
});
|
|
774
772
|
}
|
|
775
773
|
else {
|
|
776
|
-
this.logger.error('Transaction failed
|
|
774
|
+
this.logger.error('Transaction failed', { errorName: e.name });
|
|
777
775
|
throw new DynamoDBError(500, {
|
|
778
776
|
message: e.message,
|
|
779
777
|
cause: e,
|
|
@@ -822,28 +820,28 @@ class DynamoDBTransaction {
|
|
|
822
820
|
await this.txConflictRetryWrapper(command, this.txRetryLimit, this.txRetryDelay).catch((e) => {
|
|
823
821
|
this._cleanup();
|
|
824
822
|
if (isTransactionConflictError(e)) {
|
|
825
|
-
this.logger.error('
|
|
823
|
+
this.logger.error('DynamoDB transaction conflict retry limit reached', { errorName: e.name });
|
|
826
824
|
throw new DynamoDBError(409, {
|
|
827
825
|
message: e.message,
|
|
828
826
|
cause: e,
|
|
829
827
|
});
|
|
830
828
|
}
|
|
831
829
|
else if (e.name === 'TransactionCanceledException') {
|
|
832
|
-
this.logger.error(e.
|
|
830
|
+
this.logger.error('DynamoDB transaction canceled', { errorName: e.name });
|
|
833
831
|
throw new DynamoDBError(400, {
|
|
834
832
|
message: e.message,
|
|
835
833
|
cause: e,
|
|
836
834
|
});
|
|
837
835
|
}
|
|
838
836
|
else if (e.name === 'ValidationException') {
|
|
839
|
-
this.logger.error('The request parameters are invalid
|
|
837
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name });
|
|
840
838
|
throw new DynamoDBError(400, {
|
|
841
839
|
message: 'Invalid request parameters',
|
|
842
840
|
cause: e,
|
|
843
841
|
});
|
|
844
842
|
}
|
|
845
843
|
else {
|
|
846
|
-
this.logger.error('Transaction failed
|
|
844
|
+
this.logger.error('Transaction failed', { errorName: e.name });
|
|
847
845
|
throw new DynamoDBError(500, {
|
|
848
846
|
message: e.message,
|
|
849
847
|
cause: e,
|
|
@@ -856,6 +854,7 @@ class DynamoDBTransaction {
|
|
|
856
854
|
default:
|
|
857
855
|
throw new Error('Invalid transaction mode');
|
|
858
856
|
}
|
|
857
|
+
this.logger.debug('DynamoDB transaction completed', { mode: this.mode, itemCount: result.numberOfTransactItems });
|
|
859
858
|
return result;
|
|
860
859
|
}
|
|
861
860
|
}
|
|
@@ -925,8 +924,7 @@ class DynamoDBService {
|
|
|
925
924
|
ExpressionAttributeNames[attributeNameVariable] = attributeName;
|
|
926
925
|
});
|
|
927
926
|
}
|
|
928
|
-
this.logger.debug('
|
|
929
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
927
|
+
this.logger.debug('DynamoDB request started', { operation: 'getOne', tableKey });
|
|
930
928
|
const getCommand = new lib_dynamodb_1.GetCommand({
|
|
931
929
|
TableName: table.name,
|
|
932
930
|
Key: keys,
|
|
@@ -941,22 +939,14 @@ class DynamoDBService {
|
|
|
941
939
|
.send(getCommand)
|
|
942
940
|
.catch((e) => {
|
|
943
941
|
if (e.name === 'ValidationException') {
|
|
944
|
-
this.logger.error('The request parameters are invalid
|
|
945
|
-
tableKey,
|
|
946
|
-
primaryKey,
|
|
947
|
-
opts,
|
|
948
|
-
});
|
|
942
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name, tableKey });
|
|
949
943
|
throw new DynamoDBError(400, {
|
|
950
944
|
message: 'Invalid request parameters',
|
|
951
945
|
cause: e,
|
|
952
946
|
});
|
|
953
947
|
}
|
|
954
948
|
else {
|
|
955
|
-
this.logger.error('Failed to get item
|
|
956
|
-
tableKey,
|
|
957
|
-
primaryKey,
|
|
958
|
-
opts,
|
|
959
|
-
});
|
|
949
|
+
this.logger.error('Failed to get item', { errorName: e.name, tableKey });
|
|
960
950
|
throw new DynamoDBError(500, {
|
|
961
951
|
message: 'Failed to get item',
|
|
962
952
|
cause: e,
|
|
@@ -967,6 +957,7 @@ class DynamoDBService {
|
|
|
967
957
|
if (item && table.timeToLiveAttribute && item[table.timeToLiveAttribute] !== undefined) {
|
|
968
958
|
const now = getUnixTime(new Date());
|
|
969
959
|
if (item[table.timeToLiveAttribute] <= now) {
|
|
960
|
+
this.logger.debug('DynamoDB getOne completed', { tableKey, outcome: 'expired' });
|
|
970
961
|
return null;
|
|
971
962
|
}
|
|
972
963
|
// Delete the time to live attribute from the item if not requested
|
|
@@ -974,6 +965,7 @@ class DynamoDBService {
|
|
|
974
965
|
delete item[table.timeToLiveAttribute];
|
|
975
966
|
}
|
|
976
967
|
}
|
|
968
|
+
this.logger.debug('DynamoDB getOne completed', { tableKey, outcome: item ? 'found' : 'not-found' });
|
|
977
969
|
// Item will be undefined if not found
|
|
978
970
|
// Convert keys back to camelCase if camelOrSnake is 'camel' or to snake_case if camelOrSnake is 'snake'
|
|
979
971
|
if (item) {
|
|
@@ -1007,7 +999,6 @@ class DynamoDBService {
|
|
|
1007
999
|
const keys = primaryKeys.map((primaryKey, indx) => {
|
|
1008
1000
|
const isArrayOfKeys = Array.isArray(primaryKey);
|
|
1009
1001
|
const pkStr = serializeKey(primaryKey);
|
|
1010
|
-
this.logger.debug('Primary Key: %s', pkStr);
|
|
1011
1002
|
if (pkIndexMap.has(pkStr)) {
|
|
1012
1003
|
throw new Error(`Duplicate primary key: ${pkStr}`);
|
|
1013
1004
|
}
|
|
@@ -1056,13 +1047,12 @@ class DynamoDBService {
|
|
|
1056
1047
|
ExpressionAttributeNames[attributeNameVariable] = attributeName;
|
|
1057
1048
|
});
|
|
1058
1049
|
}
|
|
1059
|
-
this.logger.debug('Projection Expression: %s', ProjectionExpression);
|
|
1060
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
1061
1050
|
const results = new Array(primaryKeys.length).fill(null);
|
|
1062
1051
|
const now = getUnixTime(new Date());
|
|
1063
1052
|
let unprocessedAttempt = 0;
|
|
1064
1053
|
while (keys.length > 0) {
|
|
1065
1054
|
const batch = keys.splice(0, 100);
|
|
1055
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'getMany', tableKey, itemCount: batch.length });
|
|
1066
1056
|
const batchGetCommand = new lib_dynamodb_1.BatchGetCommand({
|
|
1067
1057
|
RequestItems: {
|
|
1068
1058
|
[table.name]: {
|
|
@@ -1076,19 +1066,23 @@ class DynamoDBService {
|
|
|
1076
1066
|
},
|
|
1077
1067
|
ReturnConsumedCapacity: opts.returnConsumedCapacity ?? client_dynamodb_1.ReturnConsumedCapacity.NONE,
|
|
1078
1068
|
});
|
|
1079
|
-
const { Responses: responses, UnprocessedKeys: rawUnprocessedKeys } = await this.ddbDocClient.send(batchGetCommand)
|
|
1069
|
+
const { Responses: responses, UnprocessedKeys: rawUnprocessedKeys } = await this.ddbDocClient.send(batchGetCommand).catch((error) => {
|
|
1070
|
+
this.logger.error('Failed to get items', { tableKey, errorName: error.name });
|
|
1071
|
+
throw error;
|
|
1072
|
+
});
|
|
1080
1073
|
const items = responses && responses[table.name] ? responses[table.name] : [];
|
|
1081
1074
|
const unprocessedKeys = (rawUnprocessedKeys && rawUnprocessedKeys[table.name]?.Keys) || [];
|
|
1082
|
-
this.logger.debug(`GetMany Responsed Items: %o`, items);
|
|
1083
|
-
this.logger.debug('Now: %d', now);
|
|
1084
1075
|
if (unprocessedKeys.length > 0) {
|
|
1085
1076
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
1077
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'getMany', tableKey, retryCount: unprocessedAttempt });
|
|
1086
1078
|
throw new DynamoDBError(500, {
|
|
1087
1079
|
message: 'Failed to retrieve items after multiple attempts',
|
|
1088
1080
|
cause: new Error('Too many unprocessed items'),
|
|
1089
1081
|
});
|
|
1090
1082
|
}
|
|
1091
|
-
|
|
1083
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
1084
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'getMany', tableKey, itemCount: unprocessedKeys.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
1085
|
+
await sleep(delayMs);
|
|
1092
1086
|
unprocessedAttempt += 1;
|
|
1093
1087
|
}
|
|
1094
1088
|
else {
|
|
@@ -1111,7 +1105,6 @@ class DynamoDBService {
|
|
|
1111
1105
|
const pkStr = serializeKey(table.sortKey
|
|
1112
1106
|
? [item[table.partitionKey], item[table.sortKey]]
|
|
1113
1107
|
: item[table.partitionKey]);
|
|
1114
|
-
this.logger.debug('Primary Key: %s', pkStr);
|
|
1115
1108
|
// Delete the primary key values from the item if not requested
|
|
1116
1109
|
if (optIncludePartitionKey === false) {
|
|
1117
1110
|
delete item[table.partitionKey];
|
|
@@ -1138,6 +1131,7 @@ class DynamoDBService {
|
|
|
1138
1131
|
}
|
|
1139
1132
|
});
|
|
1140
1133
|
}
|
|
1134
|
+
this.logger.debug('DynamoDB getMany completed', { tableKey, requestedCount: primaryKeys.length, returnedCount: results.filter(item => item !== null).length });
|
|
1141
1135
|
return results;
|
|
1142
1136
|
}
|
|
1143
1137
|
async find(tableKey, keyCondition, filter = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1300,7 +1294,6 @@ class DynamoDBService {
|
|
|
1300
1294
|
indexName: indexToScan.indexName,
|
|
1301
1295
|
projectionType: indexToScan
|
|
1302
1296
|
.projectionType,
|
|
1303
|
-
projectionFields,
|
|
1304
1297
|
});
|
|
1305
1298
|
}
|
|
1306
1299
|
break;
|
|
@@ -1330,12 +1323,11 @@ class DynamoDBService {
|
|
|
1330
1323
|
]).length > 0;
|
|
1331
1324
|
}
|
|
1332
1325
|
if (hasFieldNotInsideGSIProjection) {
|
|
1333
|
-
this.logger.error('Some of the projection fields are not included in the GSI projection,
|
|
1326
|
+
this.logger.error('Some of the projection fields are not included in the GSI projection, so the query cannot be executed', {
|
|
1334
1327
|
tableKey,
|
|
1335
1328
|
indexName: indexToScan.indexName,
|
|
1336
1329
|
projectionType: indexToScan
|
|
1337
1330
|
.projectionType,
|
|
1338
|
-
projectionFields,
|
|
1339
1331
|
});
|
|
1340
1332
|
throw new Error('All projection fields must be included in the GSI projection');
|
|
1341
1333
|
}
|
|
@@ -1392,11 +1384,6 @@ class DynamoDBService {
|
|
|
1392
1384
|
}
|
|
1393
1385
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
1394
1386
|
});
|
|
1395
|
-
this.logger.debug('Projection Expression: %s', ProjectionExpression);
|
|
1396
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
1397
|
-
this.logger.debug('Expression Attribute Values: %o', ExpressionAttributeValues);
|
|
1398
|
-
this.logger.debug('Key Condition Expression: %s', KeyConditionExpression);
|
|
1399
|
-
this.logger.debug('Filter Expression: %s', FilterExpression);
|
|
1400
1387
|
const numOfNeededItems = opts.limit; // May be undefined then no limit
|
|
1401
1388
|
const result = {
|
|
1402
1389
|
items: [],
|
|
@@ -1458,32 +1445,24 @@ class DynamoDBService {
|
|
|
1458
1445
|
: {}),
|
|
1459
1446
|
};
|
|
1460
1447
|
while (true) {
|
|
1448
|
+
this.logger.debug('DynamoDB query page requested', { tableKey, hasCursor: !!params.ExclusiveStartKey, hasFilter: !!params.FilterExpression });
|
|
1461
1449
|
const { Items, LastEvaluatedKey } = await this.ddbDocClient
|
|
1462
1450
|
.send(new lib_dynamodb_1.QueryCommand(params))
|
|
1463
1451
|
.catch((e) => {
|
|
1464
1452
|
if (e.name === 'ValidationException') {
|
|
1465
|
-
this.logger.error('The request parameters are invalid
|
|
1466
|
-
tableKey,
|
|
1467
|
-
keyCondition,
|
|
1468
|
-
filter,
|
|
1469
|
-
opts,
|
|
1470
|
-
});
|
|
1453
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name, tableKey });
|
|
1471
1454
|
throw new DynamoDBError(400, {
|
|
1472
1455
|
message: 'Invalid request parameters',
|
|
1473
1456
|
cause: e,
|
|
1474
1457
|
});
|
|
1475
1458
|
}
|
|
1476
|
-
this.logger.error('Failed to query items
|
|
1477
|
-
tableKey,
|
|
1478
|
-
keyCondition,
|
|
1479
|
-
filter,
|
|
1480
|
-
opts,
|
|
1481
|
-
});
|
|
1459
|
+
this.logger.error('Failed to query items', { errorName: e.name, tableKey });
|
|
1482
1460
|
throw new DynamoDBError(500, {
|
|
1483
1461
|
message: 'Failed to query items',
|
|
1484
1462
|
cause: e,
|
|
1485
1463
|
});
|
|
1486
1464
|
});
|
|
1465
|
+
this.logger.debug('DynamoDB query page received', { tableKey, itemCount: Items?.length ?? 0, hasMore: !!LastEvaluatedKey });
|
|
1487
1466
|
if (LastEvaluatedKey) {
|
|
1488
1467
|
params.ExclusiveStartKey = LastEvaluatedKey;
|
|
1489
1468
|
}
|
|
@@ -1628,6 +1607,7 @@ class DynamoDBService {
|
|
|
1628
1607
|
ExpressionAttributeNames[key] = value;
|
|
1629
1608
|
});
|
|
1630
1609
|
}
|
|
1610
|
+
this.logger.debug('DynamoDB request started', { operation: 'createOne', tableKey });
|
|
1631
1611
|
const putCommand = new lib_dynamodb_1.PutCommand({
|
|
1632
1612
|
TableName: table.name,
|
|
1633
1613
|
Item: normalizedItem,
|
|
@@ -1680,22 +1660,18 @@ class DynamoDBService {
|
|
|
1680
1660
|
const error = e;
|
|
1681
1661
|
if (error.name === 'ConditionalCheckFailedException' &&
|
|
1682
1662
|
opts.onConflict === 'ignore') {
|
|
1683
|
-
this.logger.debug('
|
|
1684
|
-
(table.sortKey ? `:${normalizedItem[table.sortKey]}` : ''));
|
|
1663
|
+
this.logger.debug('DynamoDB create skipped: item already exists', { tableKey });
|
|
1685
1664
|
result.created = false;
|
|
1686
1665
|
}
|
|
1687
1666
|
else {
|
|
1688
|
-
this.logger.error('Failed to create item
|
|
1689
|
-
tableKey,
|
|
1690
|
-
item: normalizedItem,
|
|
1691
|
-
opts,
|
|
1692
|
-
});
|
|
1667
|
+
this.logger.error('Failed to create item', { errorName: error.name, tableKey });
|
|
1693
1668
|
throw new DynamoDBError(500, {
|
|
1694
1669
|
message: 'Failed to create item',
|
|
1695
1670
|
cause: error,
|
|
1696
1671
|
});
|
|
1697
1672
|
}
|
|
1698
1673
|
}
|
|
1674
|
+
this.logger.debug('DynamoDB createOne completed', { tableKey, created: result.created });
|
|
1699
1675
|
return result;
|
|
1700
1676
|
}
|
|
1701
1677
|
async createMany(tableKey, primaryKeys, items, opts = {}) {
|
|
@@ -1740,6 +1716,7 @@ class DynamoDBService {
|
|
|
1740
1716
|
let unprocessedAttempt = 0;
|
|
1741
1717
|
while (normalizedItems.length > 0) {
|
|
1742
1718
|
const batch = normalizedItems.splice(0, 25);
|
|
1719
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'createMany', tableKey, itemCount: batch.length });
|
|
1743
1720
|
const batchWriteCommand = new lib_dynamodb_1.BatchWriteCommand({
|
|
1744
1721
|
RequestItems: {
|
|
1745
1722
|
[table.name]: batch.map(item => ({
|
|
@@ -1753,11 +1730,7 @@ class DynamoDBService {
|
|
|
1753
1730
|
const { UnprocessedItems } = await this.ddbDocClient
|
|
1754
1731
|
.send(batchWriteCommand)
|
|
1755
1732
|
.catch((e) => {
|
|
1756
|
-
this.logger.error('Failed to create items
|
|
1757
|
-
tableKey,
|
|
1758
|
-
items: batch,
|
|
1759
|
-
opts,
|
|
1760
|
-
});
|
|
1733
|
+
this.logger.error('Failed to create items', { errorName: e.name, tableKey });
|
|
1761
1734
|
throw new DynamoDBError(500, {
|
|
1762
1735
|
message: 'Failed to create items',
|
|
1763
1736
|
cause: e,
|
|
@@ -1766,12 +1739,15 @@ class DynamoDBService {
|
|
|
1766
1739
|
const unprocessedItems = UnprocessedItems?.[table.name];
|
|
1767
1740
|
if (unprocessedItems !== undefined && unprocessedItems.length > 0) {
|
|
1768
1741
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
1742
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'createMany', tableKey, retryCount: unprocessedAttempt });
|
|
1769
1743
|
throw new DynamoDBError(500, {
|
|
1770
1744
|
message: 'Failed to create items after multiple attempts',
|
|
1771
1745
|
cause: new Error('Too many unprocessed items'),
|
|
1772
1746
|
});
|
|
1773
1747
|
}
|
|
1774
|
-
|
|
1748
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
1749
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'createMany', tableKey, itemCount: unprocessedItems.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
1750
|
+
await sleep(delayMs);
|
|
1775
1751
|
unprocessedAttempt += 1;
|
|
1776
1752
|
normalizedItems.push(...unprocessedItems
|
|
1777
1753
|
.map(item => item.PutRequest?.Item)
|
|
@@ -1781,6 +1757,7 @@ class DynamoDBService {
|
|
|
1781
1757
|
unprocessedAttempt = 0;
|
|
1782
1758
|
}
|
|
1783
1759
|
}
|
|
1760
|
+
this.logger.debug('DynamoDB createMany completed', { tableKey, itemCount: result.numberOfItems });
|
|
1784
1761
|
return result;
|
|
1785
1762
|
}
|
|
1786
1763
|
async update(tableKey, primaryKey, commands, condition = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1856,10 +1833,7 @@ class DynamoDBService {
|
|
|
1856
1833
|
}
|
|
1857
1834
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
1858
1835
|
});
|
|
1859
|
-
this.logger.debug('
|
|
1860
|
-
this.logger.debug('Condition Expression: %s', ConditionExpression);
|
|
1861
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
1862
|
-
this.logger.debug('Expression Attribute Values: %o', ExpressionAttributeValues);
|
|
1836
|
+
this.logger.debug('DynamoDB request started', { operation: 'update', tableKey });
|
|
1863
1837
|
const updateCommand = new lib_dynamodb_1.UpdateCommand({
|
|
1864
1838
|
TableName: table.name,
|
|
1865
1839
|
Key: keys,
|
|
@@ -1893,29 +1867,17 @@ class DynamoDBService {
|
|
|
1893
1867
|
catch (e) {
|
|
1894
1868
|
const error = e;
|
|
1895
1869
|
if (error.name === 'ValidationException') {
|
|
1896
|
-
this.logger.error('The request parameters are invalid
|
|
1897
|
-
tableKey,
|
|
1898
|
-
primaryKey,
|
|
1899
|
-
commands,
|
|
1900
|
-
condition,
|
|
1901
|
-
opts,
|
|
1902
|
-
});
|
|
1870
|
+
this.logger.error('The request parameters are invalid', { errorName: error.name, tableKey });
|
|
1903
1871
|
throw new DynamoDBError(400, {
|
|
1904
1872
|
message: 'Invalid request parameters',
|
|
1905
1873
|
cause: e,
|
|
1906
1874
|
});
|
|
1907
1875
|
}
|
|
1908
1876
|
else if (error.name === 'ConditionalCheckFailedException') {
|
|
1909
|
-
this.logger.debug('
|
|
1877
|
+
this.logger.debug('DynamoDB update skipped: condition not met', { tableKey });
|
|
1910
1878
|
}
|
|
1911
1879
|
else {
|
|
1912
|
-
this.logger.error('Failed to update item
|
|
1913
|
-
tableKey,
|
|
1914
|
-
primaryKey,
|
|
1915
|
-
commands,
|
|
1916
|
-
condition,
|
|
1917
|
-
opts,
|
|
1918
|
-
});
|
|
1880
|
+
this.logger.error('Failed to update item', { errorName: error.name, tableKey });
|
|
1919
1881
|
throw new DynamoDBError(500, {
|
|
1920
1882
|
message: 'Failed to update item',
|
|
1921
1883
|
cause: e,
|
|
@@ -1941,6 +1903,7 @@ class DynamoDBService {
|
|
|
1941
1903
|
result.item = newItem;
|
|
1942
1904
|
}
|
|
1943
1905
|
}
|
|
1906
|
+
this.logger.debug('DynamoDB update completed', { tableKey, updatedOrCreated: result.updatedOrCreated });
|
|
1944
1907
|
return result;
|
|
1945
1908
|
}
|
|
1946
1909
|
async deleteOne(tableKey, primaryKey, condition = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1976,6 +1939,7 @@ class DynamoDBService {
|
|
|
1976
1939
|
}
|
|
1977
1940
|
ConditionExpression = condition.expression;
|
|
1978
1941
|
}
|
|
1942
|
+
this.logger.debug('DynamoDB request started', { operation: 'deleteOne', tableKey });
|
|
1979
1943
|
const deleteCommand = new lib_dynamodb_1.DeleteCommand({
|
|
1980
1944
|
TableName: table.name,
|
|
1981
1945
|
Key: keys,
|
|
@@ -2003,27 +1967,17 @@ class DynamoDBService {
|
|
|
2003
1967
|
catch (e) {
|
|
2004
1968
|
const error = e;
|
|
2005
1969
|
if (error.name === 'ValidationException') {
|
|
2006
|
-
this.logger.error('The request parameters are invalid
|
|
2007
|
-
tableKey,
|
|
2008
|
-
primaryKey,
|
|
2009
|
-
condition,
|
|
2010
|
-
opts,
|
|
2011
|
-
});
|
|
1970
|
+
this.logger.error('The request parameters are invalid', { errorName: error.name, tableKey });
|
|
2012
1971
|
throw new DynamoDBError(400, {
|
|
2013
1972
|
message: 'Invalid request parameters',
|
|
2014
1973
|
cause: e,
|
|
2015
1974
|
});
|
|
2016
1975
|
}
|
|
2017
1976
|
else if (error.name === 'ConditionalCheckFailedException') {
|
|
2018
|
-
this.logger.debug('
|
|
1977
|
+
this.logger.debug('DynamoDB delete skipped: condition not met', { tableKey });
|
|
2019
1978
|
}
|
|
2020
1979
|
else {
|
|
2021
|
-
this.logger.error('Failed to delete item
|
|
2022
|
-
tableKey,
|
|
2023
|
-
primaryKey,
|
|
2024
|
-
condition,
|
|
2025
|
-
opts,
|
|
2026
|
-
});
|
|
1980
|
+
this.logger.error('Failed to delete item', { errorName: error.name, tableKey });
|
|
2027
1981
|
throw new DynamoDBError(500, {
|
|
2028
1982
|
message: 'Failed to delete item',
|
|
2029
1983
|
cause: e,
|
|
@@ -2049,6 +2003,7 @@ class DynamoDBService {
|
|
|
2049
2003
|
result.item = newItem;
|
|
2050
2004
|
}
|
|
2051
2005
|
}
|
|
2006
|
+
this.logger.debug('DynamoDB deleteOne completed', { tableKey, deleted: result.deleted });
|
|
2052
2007
|
return result;
|
|
2053
2008
|
}
|
|
2054
2009
|
async deleteMany(tableKey, primaryKeys, opts = {}) {
|
|
@@ -2073,6 +2028,7 @@ class DynamoDBService {
|
|
|
2073
2028
|
let unprocessedAttempt = 0;
|
|
2074
2029
|
while (keys.length > 0) {
|
|
2075
2030
|
const batch = keys.splice(0, 25);
|
|
2031
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'deleteMany', tableKey, itemCount: batch.length });
|
|
2076
2032
|
const batchWriteCommand = new lib_dynamodb_1.BatchWriteCommand({
|
|
2077
2033
|
RequestItems: {
|
|
2078
2034
|
[table.name]: batch.map(key => ({
|
|
@@ -2086,11 +2042,7 @@ class DynamoDBService {
|
|
|
2086
2042
|
const { UnprocessedItems } = await this.ddbDocClient
|
|
2087
2043
|
.send(batchWriteCommand)
|
|
2088
2044
|
.catch((e) => {
|
|
2089
|
-
this.logger.error('Failed to delete items
|
|
2090
|
-
tableKey,
|
|
2091
|
-
keys: batch,
|
|
2092
|
-
opts,
|
|
2093
|
-
});
|
|
2045
|
+
this.logger.error('Failed to delete items', { errorName: e.name, tableKey });
|
|
2094
2046
|
throw new DynamoDBError(500, {
|
|
2095
2047
|
message: 'Failed to delete items',
|
|
2096
2048
|
cause: e,
|
|
@@ -2099,12 +2051,15 @@ class DynamoDBService {
|
|
|
2099
2051
|
const unprocessedItems = UnprocessedItems?.[table.name];
|
|
2100
2052
|
if (unprocessedItems !== undefined && unprocessedItems.length > 0) {
|
|
2101
2053
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
2054
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'deleteMany', tableKey, retryCount: unprocessedAttempt });
|
|
2102
2055
|
throw new DynamoDBError(500, {
|
|
2103
2056
|
message: 'Failed to delete items after multiple attempts',
|
|
2104
2057
|
cause: new Error('Too many unprocessed items'),
|
|
2105
2058
|
});
|
|
2106
2059
|
}
|
|
2107
|
-
|
|
2060
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
2061
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'deleteMany', tableKey, itemCount: unprocessedItems.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
2062
|
+
await sleep(delayMs);
|
|
2108
2063
|
unprocessedAttempt += 1;
|
|
2109
2064
|
keys.push(...unprocessedItems
|
|
2110
2065
|
.map(item => item.DeleteRequest?.Key)
|
|
@@ -2114,6 +2069,7 @@ class DynamoDBService {
|
|
|
2114
2069
|
unprocessedAttempt = 0;
|
|
2115
2070
|
}
|
|
2116
2071
|
}
|
|
2072
|
+
this.logger.debug('DynamoDB deleteMany completed', { tableKey, itemCount: result.numberOfItems });
|
|
2117
2073
|
return result;
|
|
2118
2074
|
}
|
|
2119
2075
|
transaction(opts) {
|