@jzhuo3/dynamodb-lib 0.1.0 → 0.1.2
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/README.md +25 -0
- package/dist/cjs/index.d.ts +22 -0
- package/dist/cjs/index.js +128 -102
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.js +129 -103
- package/dist/esm/index.js.map +1 -1
- package/doc/service.md +36 -0
- package/package.json +4 -3
package/dist/cjs/index.js
CHANGED
|
@@ -356,6 +356,55 @@ function SetDelete(attribute, value) {
|
|
|
356
356
|
const expression = `${path} :val0`;
|
|
357
357
|
return new DynamoDBExpression(expressionAttributeNameMap, expressionAttributeValueMap, expression, 'DELETE');
|
|
358
358
|
}
|
|
359
|
+
function serializeExpressionValue(value, ancestors = new Set()) {
|
|
360
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
361
|
+
return value;
|
|
362
|
+
if (typeof value === 'number') {
|
|
363
|
+
return Number.isFinite(value) ? value : { $type: 'Number', value: String(value) };
|
|
364
|
+
}
|
|
365
|
+
if (typeof value === 'bigint')
|
|
366
|
+
return { $type: 'BigInt', value: value.toString() };
|
|
367
|
+
if (value === undefined)
|
|
368
|
+
return { $type: 'Undefined' };
|
|
369
|
+
if (typeof value !== 'object') {
|
|
370
|
+
throw new TypeError(`Cannot describe expression value of type ${typeof value}`);
|
|
371
|
+
}
|
|
372
|
+
if (ancestors.has(value))
|
|
373
|
+
throw new TypeError('Cannot describe circular expression value');
|
|
374
|
+
ancestors.add(value);
|
|
375
|
+
try {
|
|
376
|
+
if (value instanceof lib_dynamodb_1.NumberValue)
|
|
377
|
+
return { $type: 'NumberValue', value: value.toString() };
|
|
378
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
379
|
+
const bytes = value instanceof ArrayBuffer
|
|
380
|
+
? Buffer.from(value)
|
|
381
|
+
: Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
382
|
+
return { $type: 'Binary', encoding: 'base64', value: bytes.toString('base64') };
|
|
383
|
+
}
|
|
384
|
+
if (value instanceof Set) {
|
|
385
|
+
return { $type: 'Set', values: Array.from(value, item => serializeExpressionValue(item, ancestors)) };
|
|
386
|
+
}
|
|
387
|
+
if (Array.isArray(value))
|
|
388
|
+
return Array.from(value, item => serializeExpressionValue(item, ancestors));
|
|
389
|
+
if (value instanceof Map) {
|
|
390
|
+
return { $type: 'Map', entries: Array.from(value, ([key, item]) => [
|
|
391
|
+
serializeExpressionValue(key, ancestors), serializeExpressionValue(item, ancestors),
|
|
392
|
+
]) };
|
|
393
|
+
}
|
|
394
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
395
|
+
throw new TypeError('Cannot describe unsupported expression value object');
|
|
396
|
+
}
|
|
397
|
+
if (Object.getOwnPropertySymbols(value).some(key => Object.prototype.propertyIsEnumerable.call(value, key))) {
|
|
398
|
+
throw new TypeError('Cannot describe expression value with symbol keys');
|
|
399
|
+
}
|
|
400
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
401
|
+
key, serializeExpressionValue(item, ancestors),
|
|
402
|
+
]));
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
ancestors.delete(value);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
359
408
|
class DynamoDBExpression {
|
|
360
409
|
expressionAttributeNameMap;
|
|
361
410
|
expressionAttributeValueMap;
|
|
@@ -369,6 +418,27 @@ class DynamoDBExpression {
|
|
|
369
418
|
this.expression = expression;
|
|
370
419
|
this.updateExpressionGroup = updateExpressionGroup;
|
|
371
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Detached, JSON-serializable description of the expression. Intended for logging,
|
|
423
|
+
* debugging, and unit tests that need to assert on a built expression
|
|
424
|
+
* without reaching into the internal Maps.
|
|
425
|
+
* Sets, bigint, binary, Maps, NumberValue, undefined and non-finite numbers
|
|
426
|
+
* use explicit `$type` tags. Circular and unsupported values throw TypeError.
|
|
427
|
+
*/
|
|
428
|
+
describe() {
|
|
429
|
+
return {
|
|
430
|
+
expression: this.expression,
|
|
431
|
+
expressionAttributeNames: Object.fromEntries(this.expressionAttributeNameMap),
|
|
432
|
+
expressionAttributeValues: this.expressionAttributeValueMap
|
|
433
|
+
? Object.fromEntries(Array.from(this.expressionAttributeValueMap, ([key, value]) => [
|
|
434
|
+
key, serializeExpressionValue(value),
|
|
435
|
+
]))
|
|
436
|
+
: {},
|
|
437
|
+
...(this.updateExpressionGroup !== undefined
|
|
438
|
+
? { updateExpressionGroup: this.updateExpressionGroup }
|
|
439
|
+
: {}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
372
442
|
}
|
|
373
443
|
exports.DynamoDBExpression = DynamoDBExpression;
|
|
374
444
|
var DynamoDBTransactionMode;
|
|
@@ -421,6 +491,7 @@ class DynamoDBTransaction {
|
|
|
421
491
|
if (i === retries - 1) {
|
|
422
492
|
throw error;
|
|
423
493
|
}
|
|
494
|
+
this.logger.warn('DynamoDB retrying transaction conflict', { mode: this.mode, attempt: i + 1, maxAttempts: retries, delayMs: delay * (i + 1) });
|
|
424
495
|
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
|
|
425
496
|
}
|
|
426
497
|
else {
|
|
@@ -669,10 +740,6 @@ class DynamoDBTransaction {
|
|
|
669
740
|
}
|
|
670
741
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
671
742
|
});
|
|
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
743
|
this.transactWriteItems.push({
|
|
677
744
|
Update: {
|
|
678
745
|
TableName: table.name,
|
|
@@ -748,6 +815,7 @@ class DynamoDBTransaction {
|
|
|
748
815
|
const result = {
|
|
749
816
|
numberOfTransactItems: 0,
|
|
750
817
|
};
|
|
818
|
+
this.logger.debug('DynamoDB transaction started', { mode: this.mode, itemCount: this.mode === DynamoDBTransactionMode.READ ? this.transactGetItems.length : this.transactWriteItems.length });
|
|
751
819
|
switch (this.mode) {
|
|
752
820
|
case DynamoDBTransactionMode.READ:
|
|
753
821
|
command = new lib_dynamodb_1.TransactGetCommand({
|
|
@@ -759,21 +827,21 @@ class DynamoDBTransaction {
|
|
|
759
827
|
const { Responses } = (await this.txConflictRetryWrapper(command, this.txRetryLimit, this.txRetryDelay).catch((e) => {
|
|
760
828
|
this._cleanup();
|
|
761
829
|
if (isTransactionConflictError(e)) {
|
|
762
|
-
this.logger.error('
|
|
830
|
+
this.logger.error('DynamoDB transaction conflict retry limit reached', { errorName: e.name });
|
|
763
831
|
throw new DynamoDBError(409, {
|
|
764
832
|
message: e.message,
|
|
765
833
|
cause: e,
|
|
766
834
|
});
|
|
767
835
|
}
|
|
768
836
|
else if (e.name === 'TransactionCanceledException') {
|
|
769
|
-
this.logger.error(e.
|
|
837
|
+
this.logger.error('DynamoDB transaction canceled', { errorName: e.name });
|
|
770
838
|
throw new DynamoDBError(400, {
|
|
771
839
|
message: e.message,
|
|
772
840
|
cause: e,
|
|
773
841
|
});
|
|
774
842
|
}
|
|
775
843
|
else {
|
|
776
|
-
this.logger.error('Transaction failed
|
|
844
|
+
this.logger.error('Transaction failed', { errorName: e.name });
|
|
777
845
|
throw new DynamoDBError(500, {
|
|
778
846
|
message: e.message,
|
|
779
847
|
cause: e,
|
|
@@ -822,28 +890,28 @@ class DynamoDBTransaction {
|
|
|
822
890
|
await this.txConflictRetryWrapper(command, this.txRetryLimit, this.txRetryDelay).catch((e) => {
|
|
823
891
|
this._cleanup();
|
|
824
892
|
if (isTransactionConflictError(e)) {
|
|
825
|
-
this.logger.error('
|
|
893
|
+
this.logger.error('DynamoDB transaction conflict retry limit reached', { errorName: e.name });
|
|
826
894
|
throw new DynamoDBError(409, {
|
|
827
895
|
message: e.message,
|
|
828
896
|
cause: e,
|
|
829
897
|
});
|
|
830
898
|
}
|
|
831
899
|
else if (e.name === 'TransactionCanceledException') {
|
|
832
|
-
this.logger.error(e.
|
|
900
|
+
this.logger.error('DynamoDB transaction canceled', { errorName: e.name });
|
|
833
901
|
throw new DynamoDBError(400, {
|
|
834
902
|
message: e.message,
|
|
835
903
|
cause: e,
|
|
836
904
|
});
|
|
837
905
|
}
|
|
838
906
|
else if (e.name === 'ValidationException') {
|
|
839
|
-
this.logger.error('The request parameters are invalid
|
|
907
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name });
|
|
840
908
|
throw new DynamoDBError(400, {
|
|
841
909
|
message: 'Invalid request parameters',
|
|
842
910
|
cause: e,
|
|
843
911
|
});
|
|
844
912
|
}
|
|
845
913
|
else {
|
|
846
|
-
this.logger.error('Transaction failed
|
|
914
|
+
this.logger.error('Transaction failed', { errorName: e.name });
|
|
847
915
|
throw new DynamoDBError(500, {
|
|
848
916
|
message: e.message,
|
|
849
917
|
cause: e,
|
|
@@ -856,6 +924,7 @@ class DynamoDBTransaction {
|
|
|
856
924
|
default:
|
|
857
925
|
throw new Error('Invalid transaction mode');
|
|
858
926
|
}
|
|
927
|
+
this.logger.debug('DynamoDB transaction completed', { mode: this.mode, itemCount: result.numberOfTransactItems });
|
|
859
928
|
return result;
|
|
860
929
|
}
|
|
861
930
|
}
|
|
@@ -925,8 +994,7 @@ class DynamoDBService {
|
|
|
925
994
|
ExpressionAttributeNames[attributeNameVariable] = attributeName;
|
|
926
995
|
});
|
|
927
996
|
}
|
|
928
|
-
this.logger.debug('
|
|
929
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
997
|
+
this.logger.debug('DynamoDB request started', { operation: 'getOne', tableKey });
|
|
930
998
|
const getCommand = new lib_dynamodb_1.GetCommand({
|
|
931
999
|
TableName: table.name,
|
|
932
1000
|
Key: keys,
|
|
@@ -941,22 +1009,14 @@ class DynamoDBService {
|
|
|
941
1009
|
.send(getCommand)
|
|
942
1010
|
.catch((e) => {
|
|
943
1011
|
if (e.name === 'ValidationException') {
|
|
944
|
-
this.logger.error('The request parameters are invalid
|
|
945
|
-
tableKey,
|
|
946
|
-
primaryKey,
|
|
947
|
-
opts,
|
|
948
|
-
});
|
|
1012
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name, tableKey });
|
|
949
1013
|
throw new DynamoDBError(400, {
|
|
950
1014
|
message: 'Invalid request parameters',
|
|
951
1015
|
cause: e,
|
|
952
1016
|
});
|
|
953
1017
|
}
|
|
954
1018
|
else {
|
|
955
|
-
this.logger.error('Failed to get item
|
|
956
|
-
tableKey,
|
|
957
|
-
primaryKey,
|
|
958
|
-
opts,
|
|
959
|
-
});
|
|
1019
|
+
this.logger.error('Failed to get item', { errorName: e.name, tableKey });
|
|
960
1020
|
throw new DynamoDBError(500, {
|
|
961
1021
|
message: 'Failed to get item',
|
|
962
1022
|
cause: e,
|
|
@@ -967,6 +1027,7 @@ class DynamoDBService {
|
|
|
967
1027
|
if (item && table.timeToLiveAttribute && item[table.timeToLiveAttribute] !== undefined) {
|
|
968
1028
|
const now = getUnixTime(new Date());
|
|
969
1029
|
if (item[table.timeToLiveAttribute] <= now) {
|
|
1030
|
+
this.logger.debug('DynamoDB getOne completed', { tableKey, outcome: 'expired' });
|
|
970
1031
|
return null;
|
|
971
1032
|
}
|
|
972
1033
|
// Delete the time to live attribute from the item if not requested
|
|
@@ -974,6 +1035,7 @@ class DynamoDBService {
|
|
|
974
1035
|
delete item[table.timeToLiveAttribute];
|
|
975
1036
|
}
|
|
976
1037
|
}
|
|
1038
|
+
this.logger.debug('DynamoDB getOne completed', { tableKey, outcome: item ? 'found' : 'not-found' });
|
|
977
1039
|
// Item will be undefined if not found
|
|
978
1040
|
// Convert keys back to camelCase if camelOrSnake is 'camel' or to snake_case if camelOrSnake is 'snake'
|
|
979
1041
|
if (item) {
|
|
@@ -1007,7 +1069,6 @@ class DynamoDBService {
|
|
|
1007
1069
|
const keys = primaryKeys.map((primaryKey, indx) => {
|
|
1008
1070
|
const isArrayOfKeys = Array.isArray(primaryKey);
|
|
1009
1071
|
const pkStr = serializeKey(primaryKey);
|
|
1010
|
-
this.logger.debug('Primary Key: %s', pkStr);
|
|
1011
1072
|
if (pkIndexMap.has(pkStr)) {
|
|
1012
1073
|
throw new Error(`Duplicate primary key: ${pkStr}`);
|
|
1013
1074
|
}
|
|
@@ -1056,13 +1117,12 @@ class DynamoDBService {
|
|
|
1056
1117
|
ExpressionAttributeNames[attributeNameVariable] = attributeName;
|
|
1057
1118
|
});
|
|
1058
1119
|
}
|
|
1059
|
-
this.logger.debug('Projection Expression: %s', ProjectionExpression);
|
|
1060
|
-
this.logger.debug('Expression Attribute Names: %o', ExpressionAttributeNames);
|
|
1061
1120
|
const results = new Array(primaryKeys.length).fill(null);
|
|
1062
1121
|
const now = getUnixTime(new Date());
|
|
1063
1122
|
let unprocessedAttempt = 0;
|
|
1064
1123
|
while (keys.length > 0) {
|
|
1065
1124
|
const batch = keys.splice(0, 100);
|
|
1125
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'getMany', tableKey, itemCount: batch.length });
|
|
1066
1126
|
const batchGetCommand = new lib_dynamodb_1.BatchGetCommand({
|
|
1067
1127
|
RequestItems: {
|
|
1068
1128
|
[table.name]: {
|
|
@@ -1076,19 +1136,23 @@ class DynamoDBService {
|
|
|
1076
1136
|
},
|
|
1077
1137
|
ReturnConsumedCapacity: opts.returnConsumedCapacity ?? client_dynamodb_1.ReturnConsumedCapacity.NONE,
|
|
1078
1138
|
});
|
|
1079
|
-
const { Responses: responses, UnprocessedKeys: rawUnprocessedKeys } = await this.ddbDocClient.send(batchGetCommand)
|
|
1139
|
+
const { Responses: responses, UnprocessedKeys: rawUnprocessedKeys } = await this.ddbDocClient.send(batchGetCommand).catch((error) => {
|
|
1140
|
+
this.logger.error('Failed to get items', { tableKey, errorName: error.name });
|
|
1141
|
+
throw error;
|
|
1142
|
+
});
|
|
1080
1143
|
const items = responses && responses[table.name] ? responses[table.name] : [];
|
|
1081
1144
|
const unprocessedKeys = (rawUnprocessedKeys && rawUnprocessedKeys[table.name]?.Keys) || [];
|
|
1082
|
-
this.logger.debug(`GetMany Responsed Items: %o`, items);
|
|
1083
|
-
this.logger.debug('Now: %d', now);
|
|
1084
1145
|
if (unprocessedKeys.length > 0) {
|
|
1085
1146
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
1147
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'getMany', tableKey, retryCount: unprocessedAttempt });
|
|
1086
1148
|
throw new DynamoDBError(500, {
|
|
1087
1149
|
message: 'Failed to retrieve items after multiple attempts',
|
|
1088
1150
|
cause: new Error('Too many unprocessed items'),
|
|
1089
1151
|
});
|
|
1090
1152
|
}
|
|
1091
|
-
|
|
1153
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
1154
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'getMany', tableKey, itemCount: unprocessedKeys.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
1155
|
+
await sleep(delayMs);
|
|
1092
1156
|
unprocessedAttempt += 1;
|
|
1093
1157
|
}
|
|
1094
1158
|
else {
|
|
@@ -1111,7 +1175,6 @@ class DynamoDBService {
|
|
|
1111
1175
|
const pkStr = serializeKey(table.sortKey
|
|
1112
1176
|
? [item[table.partitionKey], item[table.sortKey]]
|
|
1113
1177
|
: item[table.partitionKey]);
|
|
1114
|
-
this.logger.debug('Primary Key: %s', pkStr);
|
|
1115
1178
|
// Delete the primary key values from the item if not requested
|
|
1116
1179
|
if (optIncludePartitionKey === false) {
|
|
1117
1180
|
delete item[table.partitionKey];
|
|
@@ -1138,6 +1201,7 @@ class DynamoDBService {
|
|
|
1138
1201
|
}
|
|
1139
1202
|
});
|
|
1140
1203
|
}
|
|
1204
|
+
this.logger.debug('DynamoDB getMany completed', { tableKey, requestedCount: primaryKeys.length, returnedCount: results.filter(item => item !== null).length });
|
|
1141
1205
|
return results;
|
|
1142
1206
|
}
|
|
1143
1207
|
async find(tableKey, keyCondition, filter = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1300,7 +1364,6 @@ class DynamoDBService {
|
|
|
1300
1364
|
indexName: indexToScan.indexName,
|
|
1301
1365
|
projectionType: indexToScan
|
|
1302
1366
|
.projectionType,
|
|
1303
|
-
projectionFields,
|
|
1304
1367
|
});
|
|
1305
1368
|
}
|
|
1306
1369
|
break;
|
|
@@ -1330,12 +1393,11 @@ class DynamoDBService {
|
|
|
1330
1393
|
]).length > 0;
|
|
1331
1394
|
}
|
|
1332
1395
|
if (hasFieldNotInsideGSIProjection) {
|
|
1333
|
-
this.logger.error('Some of the projection fields are not included in the GSI projection,
|
|
1396
|
+
this.logger.error('Some of the projection fields are not included in the GSI projection, so the query cannot be executed', {
|
|
1334
1397
|
tableKey,
|
|
1335
1398
|
indexName: indexToScan.indexName,
|
|
1336
1399
|
projectionType: indexToScan
|
|
1337
1400
|
.projectionType,
|
|
1338
|
-
projectionFields,
|
|
1339
1401
|
});
|
|
1340
1402
|
throw new Error('All projection fields must be included in the GSI projection');
|
|
1341
1403
|
}
|
|
@@ -1392,11 +1454,6 @@ class DynamoDBService {
|
|
|
1392
1454
|
}
|
|
1393
1455
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
1394
1456
|
});
|
|
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
1457
|
const numOfNeededItems = opts.limit; // May be undefined then no limit
|
|
1401
1458
|
const result = {
|
|
1402
1459
|
items: [],
|
|
@@ -1458,32 +1515,24 @@ class DynamoDBService {
|
|
|
1458
1515
|
: {}),
|
|
1459
1516
|
};
|
|
1460
1517
|
while (true) {
|
|
1518
|
+
this.logger.debug('DynamoDB query page requested', { tableKey, hasCursor: !!params.ExclusiveStartKey, hasFilter: !!params.FilterExpression });
|
|
1461
1519
|
const { Items, LastEvaluatedKey } = await this.ddbDocClient
|
|
1462
1520
|
.send(new lib_dynamodb_1.QueryCommand(params))
|
|
1463
1521
|
.catch((e) => {
|
|
1464
1522
|
if (e.name === 'ValidationException') {
|
|
1465
|
-
this.logger.error('The request parameters are invalid
|
|
1466
|
-
tableKey,
|
|
1467
|
-
keyCondition,
|
|
1468
|
-
filter,
|
|
1469
|
-
opts,
|
|
1470
|
-
});
|
|
1523
|
+
this.logger.error('The request parameters are invalid', { errorName: e.name, tableKey });
|
|
1471
1524
|
throw new DynamoDBError(400, {
|
|
1472
1525
|
message: 'Invalid request parameters',
|
|
1473
1526
|
cause: e,
|
|
1474
1527
|
});
|
|
1475
1528
|
}
|
|
1476
|
-
this.logger.error('Failed to query items
|
|
1477
|
-
tableKey,
|
|
1478
|
-
keyCondition,
|
|
1479
|
-
filter,
|
|
1480
|
-
opts,
|
|
1481
|
-
});
|
|
1529
|
+
this.logger.error('Failed to query items', { errorName: e.name, tableKey });
|
|
1482
1530
|
throw new DynamoDBError(500, {
|
|
1483
1531
|
message: 'Failed to query items',
|
|
1484
1532
|
cause: e,
|
|
1485
1533
|
});
|
|
1486
1534
|
});
|
|
1535
|
+
this.logger.debug('DynamoDB query page received', { tableKey, itemCount: Items?.length ?? 0, hasMore: !!LastEvaluatedKey });
|
|
1487
1536
|
if (LastEvaluatedKey) {
|
|
1488
1537
|
params.ExclusiveStartKey = LastEvaluatedKey;
|
|
1489
1538
|
}
|
|
@@ -1628,6 +1677,7 @@ class DynamoDBService {
|
|
|
1628
1677
|
ExpressionAttributeNames[key] = value;
|
|
1629
1678
|
});
|
|
1630
1679
|
}
|
|
1680
|
+
this.logger.debug('DynamoDB request started', { operation: 'createOne', tableKey });
|
|
1631
1681
|
const putCommand = new lib_dynamodb_1.PutCommand({
|
|
1632
1682
|
TableName: table.name,
|
|
1633
1683
|
Item: normalizedItem,
|
|
@@ -1680,22 +1730,18 @@ class DynamoDBService {
|
|
|
1680
1730
|
const error = e;
|
|
1681
1731
|
if (error.name === 'ConditionalCheckFailedException' &&
|
|
1682
1732
|
opts.onConflict === 'ignore') {
|
|
1683
|
-
this.logger.debug('
|
|
1684
|
-
(table.sortKey ? `:${normalizedItem[table.sortKey]}` : ''));
|
|
1733
|
+
this.logger.debug('DynamoDB create skipped: item already exists', { tableKey });
|
|
1685
1734
|
result.created = false;
|
|
1686
1735
|
}
|
|
1687
1736
|
else {
|
|
1688
|
-
this.logger.error('Failed to create item
|
|
1689
|
-
tableKey,
|
|
1690
|
-
item: normalizedItem,
|
|
1691
|
-
opts,
|
|
1692
|
-
});
|
|
1737
|
+
this.logger.error('Failed to create item', { errorName: error.name, tableKey });
|
|
1693
1738
|
throw new DynamoDBError(500, {
|
|
1694
1739
|
message: 'Failed to create item',
|
|
1695
1740
|
cause: error,
|
|
1696
1741
|
});
|
|
1697
1742
|
}
|
|
1698
1743
|
}
|
|
1744
|
+
this.logger.debug('DynamoDB createOne completed', { tableKey, created: result.created });
|
|
1699
1745
|
return result;
|
|
1700
1746
|
}
|
|
1701
1747
|
async createMany(tableKey, primaryKeys, items, opts = {}) {
|
|
@@ -1740,6 +1786,7 @@ class DynamoDBService {
|
|
|
1740
1786
|
let unprocessedAttempt = 0;
|
|
1741
1787
|
while (normalizedItems.length > 0) {
|
|
1742
1788
|
const batch = normalizedItems.splice(0, 25);
|
|
1789
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'createMany', tableKey, itemCount: batch.length });
|
|
1743
1790
|
const batchWriteCommand = new lib_dynamodb_1.BatchWriteCommand({
|
|
1744
1791
|
RequestItems: {
|
|
1745
1792
|
[table.name]: batch.map(item => ({
|
|
@@ -1753,11 +1800,7 @@ class DynamoDBService {
|
|
|
1753
1800
|
const { UnprocessedItems } = await this.ddbDocClient
|
|
1754
1801
|
.send(batchWriteCommand)
|
|
1755
1802
|
.catch((e) => {
|
|
1756
|
-
this.logger.error('Failed to create items
|
|
1757
|
-
tableKey,
|
|
1758
|
-
items: batch,
|
|
1759
|
-
opts,
|
|
1760
|
-
});
|
|
1803
|
+
this.logger.error('Failed to create items', { errorName: e.name, tableKey });
|
|
1761
1804
|
throw new DynamoDBError(500, {
|
|
1762
1805
|
message: 'Failed to create items',
|
|
1763
1806
|
cause: e,
|
|
@@ -1766,12 +1809,15 @@ class DynamoDBService {
|
|
|
1766
1809
|
const unprocessedItems = UnprocessedItems?.[table.name];
|
|
1767
1810
|
if (unprocessedItems !== undefined && unprocessedItems.length > 0) {
|
|
1768
1811
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
1812
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'createMany', tableKey, retryCount: unprocessedAttempt });
|
|
1769
1813
|
throw new DynamoDBError(500, {
|
|
1770
1814
|
message: 'Failed to create items after multiple attempts',
|
|
1771
1815
|
cause: new Error('Too many unprocessed items'),
|
|
1772
1816
|
});
|
|
1773
1817
|
}
|
|
1774
|
-
|
|
1818
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
1819
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'createMany', tableKey, itemCount: unprocessedItems.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
1820
|
+
await sleep(delayMs);
|
|
1775
1821
|
unprocessedAttempt += 1;
|
|
1776
1822
|
normalizedItems.push(...unprocessedItems
|
|
1777
1823
|
.map(item => item.PutRequest?.Item)
|
|
@@ -1781,6 +1827,7 @@ class DynamoDBService {
|
|
|
1781
1827
|
unprocessedAttempt = 0;
|
|
1782
1828
|
}
|
|
1783
1829
|
}
|
|
1830
|
+
this.logger.debug('DynamoDB createMany completed', { tableKey, itemCount: result.numberOfItems });
|
|
1784
1831
|
return result;
|
|
1785
1832
|
}
|
|
1786
1833
|
async update(tableKey, primaryKey, commands, condition = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1856,10 +1903,7 @@ class DynamoDBService {
|
|
|
1856
1903
|
}
|
|
1857
1904
|
ExpressionAttributeValues[attributeValueVariable] = value;
|
|
1858
1905
|
});
|
|
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);
|
|
1906
|
+
this.logger.debug('DynamoDB request started', { operation: 'update', tableKey });
|
|
1863
1907
|
const updateCommand = new lib_dynamodb_1.UpdateCommand({
|
|
1864
1908
|
TableName: table.name,
|
|
1865
1909
|
Key: keys,
|
|
@@ -1893,29 +1937,17 @@ class DynamoDBService {
|
|
|
1893
1937
|
catch (e) {
|
|
1894
1938
|
const error = e;
|
|
1895
1939
|
if (error.name === 'ValidationException') {
|
|
1896
|
-
this.logger.error('The request parameters are invalid
|
|
1897
|
-
tableKey,
|
|
1898
|
-
primaryKey,
|
|
1899
|
-
commands,
|
|
1900
|
-
condition,
|
|
1901
|
-
opts,
|
|
1902
|
-
});
|
|
1940
|
+
this.logger.error('The request parameters are invalid', { errorName: error.name, tableKey });
|
|
1903
1941
|
throw new DynamoDBError(400, {
|
|
1904
1942
|
message: 'Invalid request parameters',
|
|
1905
1943
|
cause: e,
|
|
1906
1944
|
});
|
|
1907
1945
|
}
|
|
1908
1946
|
else if (error.name === 'ConditionalCheckFailedException') {
|
|
1909
|
-
this.logger.debug('
|
|
1947
|
+
this.logger.debug('DynamoDB update skipped: condition not met', { tableKey });
|
|
1910
1948
|
}
|
|
1911
1949
|
else {
|
|
1912
|
-
this.logger.error('Failed to update item
|
|
1913
|
-
tableKey,
|
|
1914
|
-
primaryKey,
|
|
1915
|
-
commands,
|
|
1916
|
-
condition,
|
|
1917
|
-
opts,
|
|
1918
|
-
});
|
|
1950
|
+
this.logger.error('Failed to update item', { errorName: error.name, tableKey });
|
|
1919
1951
|
throw new DynamoDBError(500, {
|
|
1920
1952
|
message: 'Failed to update item',
|
|
1921
1953
|
cause: e,
|
|
@@ -1941,6 +1973,7 @@ class DynamoDBService {
|
|
|
1941
1973
|
result.item = newItem;
|
|
1942
1974
|
}
|
|
1943
1975
|
}
|
|
1976
|
+
this.logger.debug('DynamoDB update completed', { tableKey, updatedOrCreated: result.updatedOrCreated });
|
|
1944
1977
|
return result;
|
|
1945
1978
|
}
|
|
1946
1979
|
async deleteOne(tableKey, primaryKey, condition = null, opts = {}, camelOrSnake = 'camel') {
|
|
@@ -1976,6 +2009,7 @@ class DynamoDBService {
|
|
|
1976
2009
|
}
|
|
1977
2010
|
ConditionExpression = condition.expression;
|
|
1978
2011
|
}
|
|
2012
|
+
this.logger.debug('DynamoDB request started', { operation: 'deleteOne', tableKey });
|
|
1979
2013
|
const deleteCommand = new lib_dynamodb_1.DeleteCommand({
|
|
1980
2014
|
TableName: table.name,
|
|
1981
2015
|
Key: keys,
|
|
@@ -2003,27 +2037,17 @@ class DynamoDBService {
|
|
|
2003
2037
|
catch (e) {
|
|
2004
2038
|
const error = e;
|
|
2005
2039
|
if (error.name === 'ValidationException') {
|
|
2006
|
-
this.logger.error('The request parameters are invalid
|
|
2007
|
-
tableKey,
|
|
2008
|
-
primaryKey,
|
|
2009
|
-
condition,
|
|
2010
|
-
opts,
|
|
2011
|
-
});
|
|
2040
|
+
this.logger.error('The request parameters are invalid', { errorName: error.name, tableKey });
|
|
2012
2041
|
throw new DynamoDBError(400, {
|
|
2013
2042
|
message: 'Invalid request parameters',
|
|
2014
2043
|
cause: e,
|
|
2015
2044
|
});
|
|
2016
2045
|
}
|
|
2017
2046
|
else if (error.name === 'ConditionalCheckFailedException') {
|
|
2018
|
-
this.logger.debug('
|
|
2047
|
+
this.logger.debug('DynamoDB delete skipped: condition not met', { tableKey });
|
|
2019
2048
|
}
|
|
2020
2049
|
else {
|
|
2021
|
-
this.logger.error('Failed to delete item
|
|
2022
|
-
tableKey,
|
|
2023
|
-
primaryKey,
|
|
2024
|
-
condition,
|
|
2025
|
-
opts,
|
|
2026
|
-
});
|
|
2050
|
+
this.logger.error('Failed to delete item', { errorName: error.name, tableKey });
|
|
2027
2051
|
throw new DynamoDBError(500, {
|
|
2028
2052
|
message: 'Failed to delete item',
|
|
2029
2053
|
cause: e,
|
|
@@ -2049,6 +2073,7 @@ class DynamoDBService {
|
|
|
2049
2073
|
result.item = newItem;
|
|
2050
2074
|
}
|
|
2051
2075
|
}
|
|
2076
|
+
this.logger.debug('DynamoDB deleteOne completed', { tableKey, deleted: result.deleted });
|
|
2052
2077
|
return result;
|
|
2053
2078
|
}
|
|
2054
2079
|
async deleteMany(tableKey, primaryKeys, opts = {}) {
|
|
@@ -2073,6 +2098,7 @@ class DynamoDBService {
|
|
|
2073
2098
|
let unprocessedAttempt = 0;
|
|
2074
2099
|
while (keys.length > 0) {
|
|
2075
2100
|
const batch = keys.splice(0, 25);
|
|
2101
|
+
this.logger.debug('DynamoDB batch request started', { operation: 'deleteMany', tableKey, itemCount: batch.length });
|
|
2076
2102
|
const batchWriteCommand = new lib_dynamodb_1.BatchWriteCommand({
|
|
2077
2103
|
RequestItems: {
|
|
2078
2104
|
[table.name]: batch.map(key => ({
|
|
@@ -2086,11 +2112,7 @@ class DynamoDBService {
|
|
|
2086
2112
|
const { UnprocessedItems } = await this.ddbDocClient
|
|
2087
2113
|
.send(batchWriteCommand)
|
|
2088
2114
|
.catch((e) => {
|
|
2089
|
-
this.logger.error('Failed to delete items
|
|
2090
|
-
tableKey,
|
|
2091
|
-
keys: batch,
|
|
2092
|
-
opts,
|
|
2093
|
-
});
|
|
2115
|
+
this.logger.error('Failed to delete items', { errorName: e.name, tableKey });
|
|
2094
2116
|
throw new DynamoDBError(500, {
|
|
2095
2117
|
message: 'Failed to delete items',
|
|
2096
2118
|
cause: e,
|
|
@@ -2099,12 +2121,15 @@ class DynamoDBService {
|
|
|
2099
2121
|
const unprocessedItems = UnprocessedItems?.[table.name];
|
|
2100
2122
|
if (unprocessedItems !== undefined && unprocessedItems.length > 0) {
|
|
2101
2123
|
if (unprocessedAttempt >= MAX_UNPROCESSED_ATTEMPTS) {
|
|
2124
|
+
this.logger.error('DynamoDB batch retry limit reached', { operation: 'deleteMany', tableKey, retryCount: unprocessedAttempt });
|
|
2102
2125
|
throw new DynamoDBError(500, {
|
|
2103
2126
|
message: 'Failed to delete items after multiple attempts',
|
|
2104
2127
|
cause: new Error('Too many unprocessed items'),
|
|
2105
2128
|
});
|
|
2106
2129
|
}
|
|
2107
|
-
|
|
2130
|
+
const delayMs = backoffDelayMs(unprocessedAttempt);
|
|
2131
|
+
this.logger.warn('DynamoDB retrying unprocessed batch items', { operation: 'deleteMany', tableKey, itemCount: unprocessedItems.length, attempt: unprocessedAttempt + 1, delayMs });
|
|
2132
|
+
await sleep(delayMs);
|
|
2108
2133
|
unprocessedAttempt += 1;
|
|
2109
2134
|
keys.push(...unprocessedItems
|
|
2110
2135
|
.map(item => item.DeleteRequest?.Key)
|
|
@@ -2114,6 +2139,7 @@ class DynamoDBService {
|
|
|
2114
2139
|
unprocessedAttempt = 0;
|
|
2115
2140
|
}
|
|
2116
2141
|
}
|
|
2142
|
+
this.logger.debug('DynamoDB deleteMany completed', { tableKey, itemCount: result.numberOfItems });
|
|
2117
2143
|
return result;
|
|
2118
2144
|
}
|
|
2119
2145
|
transaction(opts) {
|