@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/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('Transaction conflict due to TransactionConflict');
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.message);
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 due to %s', e.message);
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('Transaction conflict due to TransactionConflict');
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.message);
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 with error: %s', e.message);
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 due to %s', e.message);
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('Projection Expression: %s', ProjectionExpression);
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 with error: %s', e.message, {
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 with error: %s', e.message, {
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
- await sleep(backoffDelayMs(unprocessedAttempt));
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, which may cause additional read cost', {
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 with error: %s', e.message, {
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 with error: %s', e.message, {
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('Item %s already exists', normalizedItem[table.partitionKey] +
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 with error: %s', error.message, {
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 with error: %s', e.message, {
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
- await sleep(backoffDelayMs(unprocessedAttempt));
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('Update Expression: %s', UpdateExpression);
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 with error: %s', error.message, {
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('Condition failed to update item %s', primaryKey.toString());
1947
+ this.logger.debug('DynamoDB update skipped: condition not met', { tableKey });
1910
1948
  }
1911
1949
  else {
1912
- this.logger.error('Failed to update item with error: %s', error.message, {
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 with error: %s', error.message, {
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('Condition failed to delete item %s', primaryKey.toString());
2047
+ this.logger.debug('DynamoDB delete skipped: condition not met', { tableKey });
2019
2048
  }
2020
2049
  else {
2021
- this.logger.error('Failed to delete item with error: %s', error.message, {
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 with error: %s', e.message, {
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
- await sleep(backoffDelayMs(unprocessedAttempt));
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) {