dyno-table 0.1.6 → 0.1.8

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -17
  3. package/dist/builders/condition-check-builder.cjs +394 -0
  4. package/dist/builders/condition-check-builder.cjs.map +1 -0
  5. package/dist/builders/condition-check-builder.js +392 -0
  6. package/dist/builders/condition-check-builder.js.map +1 -0
  7. package/dist/builders/delete-builder.cjs +422 -0
  8. package/dist/builders/delete-builder.cjs.map +1 -0
  9. package/dist/builders/delete-builder.js +420 -0
  10. package/dist/builders/delete-builder.js.map +1 -0
  11. package/dist/builders/paginator.cjs +199 -0
  12. package/dist/builders/paginator.cjs.map +1 -0
  13. package/dist/builders/paginator.js +197 -0
  14. package/dist/builders/paginator.js.map +1 -0
  15. package/dist/builders/put-builder.cjs +468 -0
  16. package/dist/builders/put-builder.cjs.map +1 -0
  17. package/dist/builders/put-builder.js +466 -0
  18. package/dist/builders/put-builder.js.map +1 -0
  19. package/dist/builders/query-builder.cjs +674 -0
  20. package/dist/builders/query-builder.cjs.map +1 -0
  21. package/dist/builders/query-builder.js +672 -0
  22. package/dist/builders/query-builder.js.map +1 -0
  23. package/dist/builders/transaction-builder.cjs +876 -0
  24. package/dist/builders/transaction-builder.cjs.map +1 -0
  25. package/dist/builders/transaction-builder.js +874 -0
  26. package/dist/builders/transaction-builder.js.map +1 -0
  27. package/dist/builders/update-builder.cjs +662 -0
  28. package/dist/builders/update-builder.cjs.map +1 -0
  29. package/dist/builders/update-builder.js +660 -0
  30. package/dist/builders/update-builder.js.map +1 -0
  31. package/dist/conditions.cjs +59 -0
  32. package/dist/conditions.cjs.map +1 -0
  33. package/dist/conditions.js +43 -0
  34. package/dist/conditions.js.map +1 -0
  35. package/dist/entity.cjs +169 -0
  36. package/dist/entity.cjs.map +1 -0
  37. package/dist/entity.js +165 -0
  38. package/dist/entity.js.map +1 -0
  39. package/dist/index.cjs +3333 -0
  40. package/dist/index.d.cts +2971 -0
  41. package/dist/index.d.ts +1504 -1383
  42. package/dist/index.js +391 -375
  43. package/dist/standard-schema.cjs +4 -0
  44. package/dist/standard-schema.cjs.map +1 -0
  45. package/dist/standard-schema.js +3 -0
  46. package/dist/standard-schema.js.map +1 -0
  47. package/dist/table.cjs +3265 -0
  48. package/dist/table.cjs.map +1 -0
  49. package/dist/table.js +3263 -0
  50. package/dist/table.js.map +1 -0
  51. package/dist/types.cjs +4 -0
  52. package/dist/types.cjs.map +1 -0
  53. package/dist/types.js +3 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/utils/key-template.cjs +19 -0
  56. package/dist/utils/key-template.cjs.map +1 -0
  57. package/dist/utils/key-template.js +17 -0
  58. package/dist/utils/key-template.js.map +1 -0
  59. package/dist/utils/sort-key-template.cjs +19 -0
  60. package/dist/utils/sort-key-template.cjs.map +1 -0
  61. package/dist/utils/sort-key-template.js +17 -0
  62. package/dist/utils/sort-key-template.js.map +1 -0
  63. package/package.json +12 -7
@@ -0,0 +1,874 @@
1
+ // src/expression.ts
2
+ var generateAttributeName = (params, attr) => {
3
+ for (const [existingName, existingAttr] of Object.entries(params.expressionAttributeNames)) {
4
+ if (existingAttr === attr) {
5
+ return existingName;
6
+ }
7
+ }
8
+ const attrName = `#${Object.keys(params.expressionAttributeNames).length}`;
9
+ params.expressionAttributeNames[attrName] = attr;
10
+ return attrName;
11
+ };
12
+ var generateValueName = (params, value) => {
13
+ const valueName = `:${params.valueCounter.count++}`;
14
+ params.expressionAttributeValues[valueName] = value;
15
+ return valueName;
16
+ };
17
+ var validateCondition = (condition, requiresAttr = true, requiresValue = true) => {
18
+ if (requiresAttr && !condition.attr) {
19
+ throw new Error(`Attribute is required for ${condition.type} condition`);
20
+ }
21
+ if (requiresValue && condition.value === void 0) {
22
+ throw new Error(`Value is required for ${condition.type} condition`);
23
+ }
24
+ };
25
+ var buildComparisonExpression = (condition, operator, params) => {
26
+ validateCondition(condition);
27
+ if (!condition.attr) {
28
+ throw new Error(`Attribute is required for ${condition.type} condition`);
29
+ }
30
+ const attrName = generateAttributeName(params, condition.attr);
31
+ const valueName = generateValueName(params, condition.value);
32
+ return `${attrName} ${operator} ${valueName}`;
33
+ };
34
+ var buildBetweenExpression = (condition, params) => {
35
+ validateCondition(condition);
36
+ if (!condition.attr) {
37
+ throw new Error(`Attribute is required for ${condition.type} condition`);
38
+ }
39
+ if (!Array.isArray(condition.value) || condition.value.length !== 2) {
40
+ throw new Error("Between condition requires an array of two values");
41
+ }
42
+ const attrName = generateAttributeName(params, condition.attr);
43
+ const lowerName = generateValueName(params, condition.value[0]);
44
+ const upperName = generateValueName(params, condition.value[1]);
45
+ return `${attrName} BETWEEN ${lowerName} AND ${upperName}`;
46
+ };
47
+ var buildFunctionExpression = (functionName, condition, params) => {
48
+ validateCondition(condition);
49
+ if (!condition.attr) {
50
+ throw new Error(`Attribute is required for ${condition.type} condition`);
51
+ }
52
+ const attrName = generateAttributeName(params, condition.attr);
53
+ const valueName = generateValueName(params, condition.value);
54
+ return `${functionName}(${attrName}, ${valueName})`;
55
+ };
56
+ var buildAttributeFunction = (functionName, condition, params) => {
57
+ validateCondition(condition, true, false);
58
+ if (!condition.attr) {
59
+ throw new Error(`Attribute is required for ${condition.type} condition`);
60
+ }
61
+ const attrName = generateAttributeName(params, condition.attr);
62
+ return `${functionName}(${attrName})`;
63
+ };
64
+ var buildLogicalExpression = (operator, conditions, params) => {
65
+ if (!conditions || conditions.length === 0) {
66
+ throw new Error(`At least one condition is required for ${operator} expression`);
67
+ }
68
+ const expressions = conditions.map((c) => buildExpression(c, params));
69
+ return `(${expressions.join(` ${operator} `)})`;
70
+ };
71
+ var buildExpression = (condition, params) => {
72
+ if (!condition) return "";
73
+ try {
74
+ const expressionBuilders = {
75
+ eq: () => buildComparisonExpression(condition, "=", params),
76
+ ne: () => buildComparisonExpression(condition, "<>", params),
77
+ lt: () => buildComparisonExpression(condition, "<", params),
78
+ lte: () => buildComparisonExpression(condition, "<=", params),
79
+ gt: () => buildComparisonExpression(condition, ">", params),
80
+ gte: () => buildComparisonExpression(condition, ">=", params),
81
+ between: () => buildBetweenExpression(condition, params),
82
+ beginsWith: () => buildFunctionExpression("begins_with", condition, params),
83
+ contains: () => buildFunctionExpression("contains", condition, params),
84
+ attributeExists: () => buildAttributeFunction("attribute_exists", condition, params),
85
+ attributeNotExists: () => buildAttributeFunction("attribute_not_exists", condition, params),
86
+ and: () => {
87
+ if (!condition.conditions) {
88
+ throw new Error("Conditions array is required for AND operator");
89
+ }
90
+ return buildLogicalExpression("AND", condition.conditions, params);
91
+ },
92
+ or: () => {
93
+ if (!condition.conditions) {
94
+ throw new Error("Conditions array is required for OR operator");
95
+ }
96
+ return buildLogicalExpression("OR", condition.conditions, params);
97
+ },
98
+ not: () => {
99
+ if (!condition.condition) {
100
+ throw new Error("Condition is required for NOT operator");
101
+ }
102
+ return `NOT (${buildExpression(condition.condition, params)})`;
103
+ }
104
+ };
105
+ const builder = expressionBuilders[condition.type];
106
+ if (!builder) {
107
+ throw new Error(`Unknown condition type: ${condition.type}`);
108
+ }
109
+ return builder();
110
+ } catch (error) {
111
+ if (error instanceof Error) {
112
+ console.error(`Error building expression for condition type ${condition.type}:`, error.message);
113
+ } else {
114
+ console.error(`Error building expression for condition type ${condition.type}:`, error);
115
+ }
116
+ throw error;
117
+ }
118
+ };
119
+ var prepareExpressionParams = (condition) => {
120
+ if (!condition) return {};
121
+ const params = {
122
+ expressionAttributeNames: {},
123
+ expressionAttributeValues: {},
124
+ valueCounter: { count: 0 }
125
+ };
126
+ const expression = buildExpression(condition, params);
127
+ return {
128
+ expression,
129
+ names: Object.keys(params.expressionAttributeNames).length > 0 ? params.expressionAttributeNames : void 0,
130
+ values: Object.keys(params.expressionAttributeValues).length > 0 ? params.expressionAttributeValues : void 0
131
+ };
132
+ };
133
+
134
+ // src/utils/debug-expression.ts
135
+ function debugCommand(command) {
136
+ const result = {};
137
+ function replaceAliases(expressionString) {
138
+ if (!expressionString) {
139
+ return expressionString;
140
+ }
141
+ let replacedString = expressionString;
142
+ for (const alias in command.expressionAttributeNames) {
143
+ const attributeName = command.expressionAttributeNames[alias];
144
+ const regex = new RegExp(alias, "g");
145
+ replacedString = replacedString.replace(regex, attributeName);
146
+ }
147
+ for (const alias in command.expressionAttributeValues) {
148
+ let attributeValue = command.expressionAttributeValues[alias];
149
+ if (attributeValue instanceof Set) {
150
+ const array = Array.from(attributeValue);
151
+ attributeValue = `Set(${array.length}){${array.map((v) => JSON.stringify(v)).join(", ")}}`;
152
+ } else {
153
+ attributeValue = JSON.stringify(attributeValue);
154
+ }
155
+ const regex = new RegExp(alias, "g");
156
+ replacedString = replacedString.replace(regex, attributeValue);
157
+ }
158
+ return replacedString;
159
+ }
160
+ if (command.updateExpression) {
161
+ result.updateExpression = replaceAliases(command.updateExpression);
162
+ }
163
+ if (command.conditionExpression) {
164
+ result.conditionExpression = replaceAliases(command.conditionExpression);
165
+ }
166
+ if (command.filterExpression) {
167
+ result.filterExpression = replaceAliases(command.filterExpression);
168
+ }
169
+ if (command.keyConditionExpression) {
170
+ result.keyConditionExpression = replaceAliases(command.keyConditionExpression);
171
+ }
172
+ if (command.projectionExpression) {
173
+ result.projectionExpression = replaceAliases(command.projectionExpression);
174
+ }
175
+ return {
176
+ raw: command,
177
+ readable: result
178
+ };
179
+ }
180
+
181
+ // src/utils/debug-transaction.ts
182
+ function debugTransactionItem(item) {
183
+ const result = {
184
+ type: item.type,
185
+ tableName: item.params.tableName
186
+ };
187
+ if ("key" in item.params) {
188
+ result.key = item.params.key;
189
+ }
190
+ if (item.type === "Put") {
191
+ result.item = item.params.item;
192
+ }
193
+ switch (item.type) {
194
+ case "Put":
195
+ case "Delete":
196
+ case "ConditionCheck":
197
+ result.readable = debugCommand(item.params).readable;
198
+ break;
199
+ case "Update":
200
+ result.readable = debugCommand(item.params).readable;
201
+ break;
202
+ }
203
+ return result;
204
+ }
205
+ function debugTransaction(items) {
206
+ return items.map((item) => debugTransactionItem(item));
207
+ }
208
+
209
+ // src/builders/transaction-builder.ts
210
+ var TransactionBuilder = class {
211
+ items = [];
212
+ options = {};
213
+ indexConfig;
214
+ executor;
215
+ constructor(executor, indexConfig) {
216
+ this.executor = executor;
217
+ this.indexConfig = indexConfig;
218
+ }
219
+ /**
220
+ * Checks if an item with the same primary key already exists in the transaction
221
+ * @private
222
+ */
223
+ checkForDuplicateItem(tableName, newItem) {
224
+ const pkName = this.indexConfig.partitionKey;
225
+ const skName = this.indexConfig.sortKey || "";
226
+ const pkValue = newItem[pkName];
227
+ const skValue = skName ? newItem[skName] : void 0;
228
+ if (!pkValue) {
229
+ throw new Error(`Primary key value for '${pkName}' is missing`);
230
+ }
231
+ const duplicateItem = this.items.find((item) => {
232
+ let itemKey;
233
+ let itemTableName;
234
+ switch (item.type) {
235
+ case "Put":
236
+ itemTableName = item.params.tableName;
237
+ itemKey = item.params.item;
238
+ break;
239
+ case "Update":
240
+ case "Delete":
241
+ case "ConditionCheck":
242
+ itemTableName = item.params.tableName;
243
+ itemKey = item.params.key;
244
+ break;
245
+ }
246
+ if (itemTableName === tableName && itemKey) {
247
+ const itemPkValue = itemKey[pkName];
248
+ const itemSkValue = skName ? itemKey[skName] : void 0;
249
+ if (itemPkValue === pkValue) {
250
+ if (skValue === void 0 && itemSkValue === void 0) {
251
+ return true;
252
+ }
253
+ if (skValue !== void 0 && itemSkValue !== void 0 && skValue === itemSkValue) {
254
+ return true;
255
+ }
256
+ }
257
+ }
258
+ return false;
259
+ });
260
+ if (duplicateItem) {
261
+ throw new Error(
262
+ `Duplicate item detected in transaction: Table=${tableName}, ${pkName}=${String(pkValue)}, ${skName}=${skValue !== void 0 ? String(skValue) : "undefined"}. DynamoDB transactions do not allow multiple operations on the same item.`
263
+ );
264
+ }
265
+ }
266
+ /**
267
+ * Adds a put operation to the transaction.
268
+ * Use this method when you need to:
269
+ * - Insert new items as part of a transaction
270
+ * - Replace existing items atomically
271
+ * - Ensure items meet certain conditions before insertion
272
+ *
273
+ * The method automatically checks for duplicate items within the transaction
274
+ * to prevent multiple operations on the same item.
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * // Simple put operation
279
+ * transaction.put('orders', {
280
+ * orderId: '123',
281
+ * status: 'PENDING',
282
+ * amount: 100
283
+ * });
284
+ *
285
+ * // Conditional put operation
286
+ * transaction.put(
287
+ * 'inventory',
288
+ * { productId: 'ABC', quantity: 50 },
289
+ * op => op.attributeNotExists('productId')
290
+ * );
291
+ *
292
+ * // Put with complex condition
293
+ * transaction.put(
294
+ * 'users',
295
+ * { userId: '123', status: 'ACTIVE' },
296
+ * op => op.and([
297
+ * op.attributeNotExists('userId'),
298
+ * op.beginsWith('status', 'ACTIVE')
299
+ * ])
300
+ * );
301
+ * ```
302
+ *
303
+ * @param tableName - The name of the DynamoDB table
304
+ * @param item - The item to put into the table
305
+ * @param condition - Optional condition that must be satisfied
306
+ * @returns The transaction builder for method chaining
307
+ * @throws {Error} If a duplicate item is detected in the transaction
308
+ */
309
+ put(tableName, item, condition) {
310
+ this.checkForDuplicateItem(tableName, item);
311
+ const transactionItem = {
312
+ type: "Put",
313
+ params: {
314
+ tableName,
315
+ item
316
+ }
317
+ };
318
+ if (condition) {
319
+ const { expression, names, values } = prepareExpressionParams(condition);
320
+ transactionItem.params.conditionExpression = expression;
321
+ transactionItem.params.expressionAttributeNames = names;
322
+ transactionItem.params.expressionAttributeValues = values;
323
+ }
324
+ this.items.push(transactionItem);
325
+ return this;
326
+ }
327
+ /**
328
+ * Adds a pre-configured put operation to the transaction.
329
+ * Use this method when you need to:
330
+ * - Reuse put commands from PutBuilder
331
+ * - Add complex put operations with pre-configured parameters
332
+ * - Integrate with existing put command configurations
333
+ *
334
+ * This method is particularly useful when working with PutBuilder
335
+ * to maintain consistency in put operations across your application.
336
+ *
337
+ * @example
338
+ * ```typescript
339
+ * // Create a put command with PutBuilder
340
+ * const putCommand = new PutBuilder(executor, newItem, 'users')
341
+ * .condition(op => op.attributeNotExists('userId'))
342
+ * .toDynamoCommand();
343
+ *
344
+ * // Add the command to the transaction
345
+ * transaction.putWithCommand(putCommand);
346
+ * ```
347
+ *
348
+ * @param command - The complete put command configuration
349
+ * @returns The transaction builder for method chaining
350
+ * @throws {Error} If a duplicate item is detected in the transaction
351
+ * @see PutBuilder for creating put commands
352
+ */
353
+ putWithCommand(command) {
354
+ this.checkForDuplicateItem(command.tableName, command.item);
355
+ const transactionItem = {
356
+ type: "Put",
357
+ params: command
358
+ };
359
+ this.items.push(transactionItem);
360
+ return this;
361
+ }
362
+ /**
363
+ * Adds a delete operation to the transaction.
364
+ * Use this method when you need to:
365
+ * - Remove items as part of a transaction
366
+ * - Conditionally delete items
367
+ * - Ensure items exist before deletion
368
+ *
369
+ * The method automatically checks for duplicate items within the transaction
370
+ * to prevent multiple operations on the same item.
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * // Simple delete operation
375
+ * transaction.delete('orders', {
376
+ * pk: 'ORDER#123',
377
+ * sk: 'METADATA'
378
+ * });
379
+ *
380
+ * // Conditional delete operation
381
+ * transaction.delete(
382
+ * 'users',
383
+ * { pk: 'USER#123' },
384
+ * op => op.eq('status', 'INACTIVE')
385
+ * );
386
+ *
387
+ * // Delete with complex condition
388
+ * transaction.delete(
389
+ * 'products',
390
+ * { pk: 'PROD#ABC' },
391
+ * op => op.and([
392
+ * op.eq('status', 'DRAFT'),
393
+ * op.lt('version', 5)
394
+ * ])
395
+ * );
396
+ * ```
397
+ *
398
+ * @param tableName - The name of the DynamoDB table
399
+ * @param key - The primary key of the item to delete
400
+ * @param condition - Optional condition that must be satisfied
401
+ * @returns The transaction builder for method chaining
402
+ * @throws {Error} If a duplicate item is detected in the transaction
403
+ */
404
+ delete(tableName, key, condition) {
405
+ this.checkForDuplicateItem(tableName, key);
406
+ const transactionItem = {
407
+ type: "Delete",
408
+ params: {
409
+ tableName,
410
+ key: {
411
+ pk: key.pk,
412
+ sk: key.sk
413
+ }
414
+ }
415
+ };
416
+ if (condition) {
417
+ const { expression, names, values } = prepareExpressionParams(condition);
418
+ transactionItem.params.conditionExpression = expression;
419
+ transactionItem.params.expressionAttributeNames = names;
420
+ transactionItem.params.expressionAttributeValues = values;
421
+ }
422
+ this.items.push(transactionItem);
423
+ return this;
424
+ }
425
+ /**
426
+ * Adds a pre-configured delete operation to the transaction.
427
+ * Use this method when you need to:
428
+ * - Reuse delete commands from DeleteBuilder
429
+ * - Add complex delete operations with pre-configured parameters
430
+ * - Integrate with existing delete command configurations
431
+ *
432
+ * This method is particularly useful when working with DeleteBuilder
433
+ * to maintain consistency in delete operations across your application.
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * // Create a delete command with DeleteBuilder
438
+ * const deleteCommand = new DeleteBuilder(executor, 'users', { pk: 'USER#123' })
439
+ * .condition(op => op.and([
440
+ * op.attributeExists('pk'),
441
+ * op.eq('status', 'INACTIVE')
442
+ * ]))
443
+ * .toDynamoCommand();
444
+ *
445
+ * // Add the command to the transaction
446
+ * transaction.deleteWithCommand(deleteCommand);
447
+ * ```
448
+ *
449
+ * @param command - The complete delete command configuration
450
+ * @returns The transaction builder for method chaining
451
+ * @throws {Error} If a duplicate item is detected in the transaction
452
+ * @see DeleteBuilder for creating delete commands
453
+ */
454
+ deleteWithCommand(command) {
455
+ this.checkForDuplicateItem(command.tableName, command.key);
456
+ const transactionItem = {
457
+ type: "Delete",
458
+ params: command
459
+ };
460
+ this.items.push(transactionItem);
461
+ return this;
462
+ }
463
+ /**
464
+ * Adds an update operation to the transaction.
465
+ * Use this method when you need to:
466
+ * - Modify existing items as part of a transaction
467
+ * - Update multiple attributes atomically
468
+ * - Apply conditional updates
469
+ * - Perform complex attribute manipulations
470
+ *
471
+ * The method supports all DynamoDB update expressions:
472
+ * - SET: Modify or add attributes
473
+ * - REMOVE: Delete attributes
474
+ * - ADD: Update numbers and sets
475
+ * - DELETE: Remove elements from a set
476
+ *
477
+ * @example
478
+ * ```typescript
479
+ * // Simple update
480
+ * transaction.update(
481
+ * 'orders',
482
+ * { pk: 'ORDER#123' },
483
+ * 'SET #status = :status',
484
+ * { '#status': 'status' },
485
+ * { ':status': 'PROCESSING' }
486
+ * );
487
+ *
488
+ * // Complex update with multiple operations
489
+ * transaction.update(
490
+ * 'products',
491
+ * { pk: 'PROD#ABC' },
492
+ * 'SET #qty = #qty - :amount, #status = :status REMOVE #oldAttr',
493
+ * { '#qty': 'quantity', '#status': 'status', '#oldAttr': 'deprecated_field' },
494
+ * { ':amount': 1, ':status': 'LOW_STOCK' }
495
+ * );
496
+ *
497
+ * // Conditional update
498
+ * transaction.update(
499
+ * 'users',
500
+ * { pk: 'USER#123' },
501
+ * 'SET #lastLogin = :now',
502
+ * { '#lastLogin': 'lastLoginDate' },
503
+ * { ':now': new Date().toISOString() },
504
+ * op => op.attributeExists('pk')
505
+ * );
506
+ * ```
507
+ *
508
+ * @param tableName - The name of the DynamoDB table
509
+ * @param key - The primary key of the item to update
510
+ * @param updateExpression - The update expression (SET, REMOVE, ADD, DELETE)
511
+ * @param expressionAttributeNames - Map of attribute name placeholders to actual names
512
+ * @param expressionAttributeValues - Map of value placeholders to actual values
513
+ * @param condition - Optional condition that must be satisfied
514
+ * @returns The transaction builder for method chaining
515
+ * @throws {Error} If a duplicate item is detected in the transaction
516
+ */
517
+ update(tableName, key, updateExpression, expressionAttributeNames, expressionAttributeValues, condition) {
518
+ this.checkForDuplicateItem(tableName, key);
519
+ const transactionItem = {
520
+ type: "Update",
521
+ params: {
522
+ tableName,
523
+ key: {
524
+ pk: key.pk,
525
+ sk: key.sk
526
+ },
527
+ updateExpression,
528
+ expressionAttributeNames,
529
+ expressionAttributeValues
530
+ }
531
+ };
532
+ if (condition) {
533
+ const { expression, names, values } = prepareExpressionParams(condition);
534
+ transactionItem.params.conditionExpression = expression;
535
+ transactionItem.params.expressionAttributeNames = {
536
+ ...transactionItem.params.expressionAttributeNames,
537
+ ...names
538
+ };
539
+ transactionItem.params.expressionAttributeValues = {
540
+ ...transactionItem.params.expressionAttributeValues,
541
+ ...values
542
+ };
543
+ }
544
+ this.items.push(transactionItem);
545
+ return this;
546
+ }
547
+ /**
548
+ * Adds a pre-configured update operation to the transaction.
549
+ * Use this method when you need to:
550
+ * - Reuse update commands from UpdateBuilder
551
+ * - Add complex update operations with pre-configured parameters
552
+ * - Integrate with existing update command configurations
553
+ *
554
+ * This method is particularly useful when working with UpdateBuilder
555
+ * to maintain consistency in update operations across your application.
556
+ *
557
+ * @example
558
+ * ```typescript
559
+ * // Create an update command with UpdateBuilder
560
+ * const updateCommand = new UpdateBuilder(executor, 'inventory', { pk: 'PROD#ABC' })
561
+ * .set('quantity', ':qty')
562
+ * .set('lastUpdated', ':now')
563
+ * .values({
564
+ * ':qty': 100,
565
+ * ':now': new Date().toISOString()
566
+ * })
567
+ * .condition(op => op.gt('quantity', 0))
568
+ * .toDynamoCommand();
569
+ *
570
+ * // Add the command to the transaction
571
+ * transaction.updateWithCommand(updateCommand);
572
+ * ```
573
+ *
574
+ * @param command - The complete update command configuration
575
+ * @returns The transaction builder for method chaining
576
+ * @throws {Error} If a duplicate item is detected in the transaction
577
+ * @see UpdateBuilder for creating update commands
578
+ */
579
+ updateWithCommand(command) {
580
+ this.checkForDuplicateItem(command.tableName, command.key);
581
+ const transactionItem = {
582
+ type: "Update",
583
+ params: command
584
+ };
585
+ this.items.push(transactionItem);
586
+ return this;
587
+ }
588
+ /**
589
+ * Adds a condition check operation to the transaction.
590
+ * Use this method when you need to:
591
+ * - Validate item state without modifying it
592
+ * - Ensure data consistency across tables
593
+ * - Implement complex business rules
594
+ * - Verify preconditions for other operations
595
+ *
596
+ * Condition checks are particularly useful for:
597
+ * - Implementing optimistic locking
598
+ * - Ensuring referential integrity
599
+ * - Validating business rules atomically
600
+ *
601
+ * @example
602
+ * ```typescript
603
+ * // Check if order is in correct state
604
+ * transaction.conditionCheck(
605
+ * 'orders',
606
+ * { pk: 'ORDER#123' },
607
+ * op => op.eq('status', 'PENDING')
608
+ * );
609
+ *
610
+ * // Complex condition check
611
+ * transaction.conditionCheck(
612
+ * 'inventory',
613
+ * { pk: 'PROD#ABC' },
614
+ * op => op.and([
615
+ * op.gt('quantity', 0),
616
+ * op.eq('status', 'ACTIVE'),
617
+ * op.attributeExists('lastRestockDate')
618
+ * ])
619
+ * );
620
+ *
621
+ * // Check with multiple attributes
622
+ * transaction.conditionCheck(
623
+ * 'users',
624
+ * { pk: 'USER#123' },
625
+ * op => op.or([
626
+ * op.eq('status', 'PREMIUM'),
627
+ * op.gte('credits', 100)
628
+ * ])
629
+ * );
630
+ * ```
631
+ *
632
+ * @param tableName - The name of the DynamoDB table
633
+ * @param key - The primary key of the item to check
634
+ * @param condition - The condition that must be satisfied
635
+ * @returns The transaction builder for method chaining
636
+ * @throws {Error} If a duplicate item is detected in the transaction
637
+ * @throws {Error} If condition expression generation fails
638
+ */
639
+ conditionCheck(tableName, key, condition) {
640
+ this.checkForDuplicateItem(tableName, key);
641
+ const { expression, names, values } = prepareExpressionParams(condition);
642
+ if (!expression) {
643
+ throw new Error("Failed to generate condition expression");
644
+ }
645
+ const transactionItem = {
646
+ type: "ConditionCheck",
647
+ params: {
648
+ tableName,
649
+ key: {
650
+ pk: key.pk,
651
+ sk: key.sk
652
+ },
653
+ conditionExpression: expression,
654
+ expressionAttributeNames: names,
655
+ expressionAttributeValues: values
656
+ }
657
+ };
658
+ this.items.push(transactionItem);
659
+ return this;
660
+ }
661
+ /**
662
+ * Adds a pre-configured condition check operation to the transaction.
663
+ * Use this method when you need to:
664
+ * - Reuse condition checks from ConditionCheckBuilder
665
+ * - Add complex condition checks with pre-configured parameters
666
+ * - Integrate with existing condition check configurations
667
+ *
668
+ * This method is particularly useful when working with ConditionCheckBuilder
669
+ * to maintain consistency in condition checks across your application.
670
+ *
671
+ * @example
672
+ * ```typescript
673
+ * // Create a condition check with ConditionCheckBuilder
674
+ * const checkCommand = new ConditionCheckBuilder('inventory', { pk: 'PROD#ABC' })
675
+ * .condition(op => op.and([
676
+ * op.between('quantity', 10, 100),
677
+ * op.beginsWith('category', 'ELECTRONICS'),
678
+ * op.attributeExists('lastAuditDate')
679
+ * ]))
680
+ * .toDynamoCommand();
681
+ *
682
+ * // Add the command to the transaction
683
+ * transaction.conditionCheckWithCommand(checkCommand);
684
+ * ```
685
+ *
686
+ * @param command - The complete condition check command configuration
687
+ * @returns The transaction builder for method chaining
688
+ * @throws {Error} If a duplicate item is detected in the transaction
689
+ * @see ConditionCheckBuilder for creating condition check commands
690
+ */
691
+ conditionCheckWithCommand(command) {
692
+ this.checkForDuplicateItem(command.tableName, command.key);
693
+ const transactionItem = {
694
+ type: "ConditionCheck",
695
+ params: command
696
+ };
697
+ this.items.push(transactionItem);
698
+ return this;
699
+ }
700
+ /**
701
+ * Sets options for the transaction execution.
702
+ * Use this method when you need to:
703
+ * - Enable idempotent transactions
704
+ * - Track consumed capacity
705
+ * - Monitor item collection metrics
706
+ *
707
+ * @example
708
+ * ```typescript
709
+ * // Enable idempotency and capacity tracking
710
+ * transaction.withOptions({
711
+ * clientRequestToken: 'unique-request-id-123',
712
+ * returnConsumedCapacity: 'TOTAL'
713
+ * });
714
+ *
715
+ * // Track item collection metrics
716
+ * transaction.withOptions({
717
+ * returnItemCollectionMetrics: 'SIZE'
718
+ * });
719
+ * ```
720
+ *
721
+ * Note: ClientRequestToken can be used to make transactions idempotent,
722
+ * ensuring the same transaction is not executed multiple times.
723
+ *
724
+ * @param options - Configuration options for the transaction
725
+ * @returns The transaction builder for method chaining
726
+ */
727
+ withOptions(options) {
728
+ this.options = { ...this.options, ...options };
729
+ return this;
730
+ }
731
+ /**
732
+ * Gets a human-readable representation of the transaction items.
733
+ * Use this method when you need to:
734
+ * - Debug complex transactions
735
+ * - Verify operation parameters
736
+ * - Log transaction details
737
+ * - Troubleshoot condition expressions
738
+ *
739
+ * The method resolves all expression placeholders with their actual values,
740
+ * making it easier to understand the transaction's operations.
741
+ *
742
+ * @example
743
+ * ```typescript
744
+ * // Add multiple operations
745
+ * transaction
746
+ * .put('orders', { orderId: '123', status: 'PENDING' })
747
+ * .update('inventory',
748
+ * { productId: 'ABC' },
749
+ * 'SET quantity = quantity - :amount',
750
+ * undefined,
751
+ * { ':amount': 1 }
752
+ * );
753
+ *
754
+ * // Debug the transaction
755
+ * const debugInfo = transaction.debug();
756
+ * console.log('Transaction operations:', debugInfo);
757
+ * ```
758
+ *
759
+ * @returns An array of readable representations of the transaction items
760
+ */
761
+ debug() {
762
+ return debugTransaction(this.items);
763
+ }
764
+ /**
765
+ * Executes all operations in the transaction atomically.
766
+ * Use this method when you need to:
767
+ * - Perform multiple operations atomically
768
+ * - Ensure all-or-nothing execution
769
+ * - Maintain data consistency across operations
770
+ *
771
+ * The transaction will only succeed if all operations succeed.
772
+ * If any operation fails, the entire transaction is rolled back.
773
+ *
774
+ * @example
775
+ * ```typescript
776
+ * try {
777
+ * // Build and execute transaction
778
+ * await transaction
779
+ * .put('orders', newOrder)
780
+ * .update('inventory',
781
+ * { productId: 'ABC' },
782
+ * 'SET quantity = quantity - :qty',
783
+ * undefined,
784
+ * { ':qty': 1 }
785
+ * )
786
+ * .conditionCheck('products',
787
+ * { productId: 'ABC' },
788
+ * op => op.eq('status', 'ACTIVE')
789
+ * )
790
+ * .execute();
791
+ *
792
+ * console.log('Transaction completed successfully');
793
+ * } catch (error) {
794
+ * // Handle transaction failure
795
+ * console.error('Transaction failed:', error);
796
+ * }
797
+ * ```
798
+ *
799
+ * @throws {Error} If no transaction items are specified
800
+ * @throws {Error} If any operation in the transaction fails
801
+ * @returns A promise that resolves when the transaction completes
802
+ */
803
+ async execute() {
804
+ if (this.items.length === 0) {
805
+ throw new Error("No transaction items specified");
806
+ }
807
+ const transactItems = this.items.map((item) => {
808
+ switch (item.type) {
809
+ case "Put":
810
+ return {
811
+ Put: {
812
+ TableName: item.params.tableName,
813
+ Item: item.params.item,
814
+ ConditionExpression: item.params.conditionExpression,
815
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
816
+ ExpressionAttributeValues: item.params.expressionAttributeValues
817
+ }
818
+ };
819
+ case "Delete":
820
+ return {
821
+ Delete: {
822
+ TableName: item.params.tableName,
823
+ Key: item.params.key,
824
+ ConditionExpression: item.params.conditionExpression,
825
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
826
+ ExpressionAttributeValues: item.params.expressionAttributeValues
827
+ }
828
+ };
829
+ case "Update":
830
+ return {
831
+ Update: {
832
+ TableName: item.params.tableName,
833
+ Key: item.params.key,
834
+ UpdateExpression: item.params.updateExpression,
835
+ ConditionExpression: item.params.conditionExpression,
836
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
837
+ ExpressionAttributeValues: item.params.expressionAttributeValues
838
+ }
839
+ };
840
+ case "ConditionCheck":
841
+ return {
842
+ ConditionCheck: {
843
+ TableName: item.params.tableName,
844
+ Key: item.params.key,
845
+ ConditionExpression: item.params.conditionExpression,
846
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
847
+ ExpressionAttributeValues: item.params.expressionAttributeValues
848
+ }
849
+ };
850
+ default: {
851
+ const exhaustiveCheck = item;
852
+ throw new Error(`Unsupported transaction item type: ${String(exhaustiveCheck)}`);
853
+ }
854
+ }
855
+ });
856
+ const params = {
857
+ TransactItems: transactItems,
858
+ ClientRequestToken: this.options.clientRequestToken,
859
+ ReturnConsumedCapacity: this.options.returnConsumedCapacity,
860
+ ReturnItemCollectionMetrics: this.options.returnItemCollectionMetrics
861
+ };
862
+ try {
863
+ await this.executor(params);
864
+ } catch (error) {
865
+ console.log(this.debug());
866
+ console.error("Error executing transaction:", error);
867
+ throw error;
868
+ }
869
+ }
870
+ };
871
+
872
+ export { TransactionBuilder };
873
+ //# sourceMappingURL=transaction-builder.js.map
874
+ //# sourceMappingURL=transaction-builder.js.map