dyno-table 1.0.0-alpha.1 → 1.1.0

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 (96) hide show
  1. package/README.md +752 -172
  2. package/dist/builder-types-C_PDZhnP.d.ts +118 -0
  3. package/dist/builder-types-DtwbqMeF.d.cts +118 -0
  4. package/dist/builders/condition-check-builder.cjs +394 -0
  5. package/dist/builders/condition-check-builder.cjs.map +1 -0
  6. package/dist/builders/condition-check-builder.d.cts +157 -0
  7. package/dist/builders/condition-check-builder.d.ts +157 -0
  8. package/dist/builders/condition-check-builder.js +392 -0
  9. package/dist/builders/condition-check-builder.js.map +1 -0
  10. package/dist/builders/delete-builder.cjs +405 -0
  11. package/dist/builders/delete-builder.cjs.map +1 -0
  12. package/dist/builders/delete-builder.d.cts +166 -0
  13. package/dist/builders/delete-builder.d.ts +166 -0
  14. package/dist/builders/delete-builder.js +403 -0
  15. package/dist/builders/delete-builder.js.map +1 -0
  16. package/dist/builders/paginator.cjs +199 -0
  17. package/dist/builders/paginator.cjs.map +1 -0
  18. package/dist/builders/paginator.d.cts +179 -0
  19. package/dist/builders/paginator.d.ts +179 -0
  20. package/dist/builders/paginator.js +197 -0
  21. package/dist/builders/paginator.js.map +1 -0
  22. package/dist/builders/put-builder.cjs +476 -0
  23. package/dist/builders/put-builder.cjs.map +1 -0
  24. package/dist/builders/put-builder.d.cts +274 -0
  25. package/dist/builders/put-builder.d.ts +274 -0
  26. package/dist/builders/put-builder.js +474 -0
  27. package/dist/builders/put-builder.js.map +1 -0
  28. package/dist/builders/query-builder.cjs +674 -0
  29. package/dist/builders/query-builder.cjs.map +1 -0
  30. package/dist/builders/query-builder.d.cts +6 -0
  31. package/dist/builders/query-builder.d.ts +6 -0
  32. package/dist/builders/query-builder.js +672 -0
  33. package/dist/builders/query-builder.js.map +1 -0
  34. package/dist/builders/transaction-builder.cjs +918 -0
  35. package/dist/builders/transaction-builder.cjs.map +1 -0
  36. package/dist/builders/transaction-builder.d.cts +511 -0
  37. package/dist/builders/transaction-builder.d.ts +511 -0
  38. package/dist/builders/transaction-builder.js +916 -0
  39. package/dist/builders/transaction-builder.js.map +1 -0
  40. package/dist/builders/update-builder.cjs +627 -0
  41. package/dist/builders/update-builder.cjs.map +1 -0
  42. package/dist/builders/update-builder.d.cts +365 -0
  43. package/dist/builders/update-builder.d.ts +365 -0
  44. package/dist/builders/update-builder.js +625 -0
  45. package/dist/builders/update-builder.js.map +1 -0
  46. package/dist/conditions--ld9a78i.d.ts +331 -0
  47. package/dist/conditions-ChhQWd6z.d.cts +331 -0
  48. package/dist/conditions.cjs +59 -0
  49. package/dist/conditions.cjs.map +1 -0
  50. package/dist/conditions.d.cts +3 -0
  51. package/dist/conditions.d.ts +3 -0
  52. package/dist/conditions.js +43 -0
  53. package/dist/conditions.js.map +1 -0
  54. package/dist/entity.cjs +211 -0
  55. package/dist/entity.cjs.map +1 -0
  56. package/dist/entity.d.cts +149 -0
  57. package/dist/entity.d.ts +149 -0
  58. package/dist/entity.js +207 -0
  59. package/dist/entity.js.map +1 -0
  60. package/dist/query-builder-Csror9Iu.d.ts +507 -0
  61. package/dist/query-builder-D2FM9rsu.d.cts +507 -0
  62. package/dist/standard-schema.cjs +4 -0
  63. package/dist/standard-schema.cjs.map +1 -0
  64. package/dist/standard-schema.d.cts +57 -0
  65. package/dist/standard-schema.d.ts +57 -0
  66. package/dist/standard-schema.js +3 -0
  67. package/dist/standard-schema.js.map +1 -0
  68. package/dist/table-BEhBPy2G.d.cts +364 -0
  69. package/dist/table-BW3cmUqr.d.ts +364 -0
  70. package/dist/{index.cjs → table.cjs} +108 -175
  71. package/dist/table.cjs.map +1 -0
  72. package/dist/table.d.cts +12 -0
  73. package/dist/table.d.ts +12 -0
  74. package/dist/{index.js → table.js} +107 -127
  75. package/dist/table.js.map +1 -0
  76. package/dist/types.cjs +4 -0
  77. package/dist/types.cjs.map +1 -0
  78. package/dist/types.d.cts +22 -0
  79. package/dist/types.d.ts +22 -0
  80. package/dist/types.js +3 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/utils/partition-key-template.cjs +19 -0
  83. package/dist/utils/partition-key-template.cjs.map +1 -0
  84. package/dist/utils/partition-key-template.d.cts +32 -0
  85. package/dist/utils/partition-key-template.d.ts +32 -0
  86. package/dist/utils/partition-key-template.js +17 -0
  87. package/dist/utils/partition-key-template.js.map +1 -0
  88. package/dist/utils/sort-key-template.cjs +19 -0
  89. package/dist/utils/sort-key-template.cjs.map +1 -0
  90. package/dist/utils/sort-key-template.d.cts +35 -0
  91. package/dist/utils/sort-key-template.d.ts +35 -0
  92. package/dist/utils/sort-key-template.js +17 -0
  93. package/dist/utils/sort-key-template.js.map +1 -0
  94. package/package.json +77 -7
  95. package/dist/index.d.cts +0 -2971
  96. package/dist/index.d.ts +0 -2971
@@ -0,0 +1,916 @@
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
+ createKeyForPrimaryIndex(key) {
267
+ const keyCondition = {
268
+ [this.indexConfig.partitionKey]: key.pk
269
+ };
270
+ if (this.indexConfig.sortKey) {
271
+ if (key.sk === void 0) {
272
+ throw new Error("Sort key is required for delete operation");
273
+ }
274
+ keyCondition[this.indexConfig.sortKey] = key.sk;
275
+ }
276
+ return keyCondition;
277
+ }
278
+ /**
279
+ * Adds a put operation to the transaction.
280
+ * Use this method when you need to:
281
+ * - Insert new items as part of a transaction
282
+ * - Replace existing items atomically
283
+ * - Ensure items meet certain conditions before insertion
284
+ *
285
+ * The method automatically checks for duplicate items within the transaction
286
+ * to prevent multiple operations on the same item.
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * // Simple put operation
291
+ * transaction.put('orders', {
292
+ * orderId: '123',
293
+ * status: 'PENDING',
294
+ * amount: 100
295
+ * });
296
+ *
297
+ * // Conditional put operation
298
+ * transaction.put(
299
+ * 'inventory',
300
+ * { productId: 'ABC', quantity: 50 },
301
+ * op => op.attributeNotExists('productId')
302
+ * );
303
+ *
304
+ * // Put with complex condition
305
+ * transaction.put(
306
+ * 'users',
307
+ * { userId: '123', status: 'ACTIVE' },
308
+ * op => op.and([
309
+ * op.attributeNotExists('userId'),
310
+ * op.beginsWith('status', 'ACTIVE')
311
+ * ])
312
+ * );
313
+ * ```
314
+ *
315
+ * @param tableName - The name of the DynamoDB table
316
+ * @param item - The item to put into the table
317
+ * @param condition - Optional condition that must be satisfied
318
+ * @returns The transaction builder for method chaining
319
+ * @throws {Error} If a duplicate item is detected in the transaction
320
+ */
321
+ put(tableName, item, condition) {
322
+ this.checkForDuplicateItem(tableName, item);
323
+ const transactionItem = {
324
+ type: "Put",
325
+ params: {
326
+ tableName,
327
+ item
328
+ }
329
+ };
330
+ if (condition) {
331
+ const { expression, names, values } = prepareExpressionParams(condition);
332
+ transactionItem.params.conditionExpression = expression;
333
+ transactionItem.params.expressionAttributeNames = names;
334
+ transactionItem.params.expressionAttributeValues = values;
335
+ }
336
+ this.items.push(transactionItem);
337
+ return this;
338
+ }
339
+ /**
340
+ * Adds a pre-configured put operation to the transaction.
341
+ * Use this method when you need to:
342
+ * - Reuse put commands from PutBuilder
343
+ * - Add complex put operations with pre-configured parameters
344
+ * - Integrate with existing put command configurations
345
+ *
346
+ * This method is particularly useful when working with PutBuilder
347
+ * to maintain consistency in put operations across your application.
348
+ *
349
+ * @example
350
+ * ```typescript
351
+ * // Create a put command with PutBuilder
352
+ * const putCommand = new PutBuilder(executor, newItem, 'users')
353
+ * .condition(op => op.attributeNotExists('userId'))
354
+ * .toDynamoCommand();
355
+ *
356
+ * // Add the command to the transaction
357
+ * transaction.putWithCommand(putCommand);
358
+ * ```
359
+ *
360
+ * @param command - The complete put command configuration
361
+ * @returns The transaction builder for method chaining
362
+ * @throws {Error} If a duplicate item is detected in the transaction
363
+ * @see PutBuilder for creating put commands
364
+ */
365
+ putWithCommand(command) {
366
+ this.checkForDuplicateItem(command.tableName, command.item);
367
+ const transactionItem = {
368
+ type: "Put",
369
+ params: command
370
+ };
371
+ this.items.push(transactionItem);
372
+ return this;
373
+ }
374
+ /**
375
+ * Adds a delete operation to the transaction.
376
+ * Use this method when you need to:
377
+ * - Remove items as part of a transaction
378
+ * - Conditionally delete items
379
+ * - Ensure items exist before deletion
380
+ *
381
+ * The method automatically checks for duplicate items within the transaction
382
+ * to prevent multiple operations on the same item.
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * // Simple delete operation
387
+ * transaction.delete('orders', {
388
+ * pk: 'ORDER#123',
389
+ * sk: 'METADATA'
390
+ * });
391
+ *
392
+ * // Conditional delete operation
393
+ * transaction.delete(
394
+ * 'users',
395
+ * { pk: 'USER#123' },
396
+ * op => op.eq('status', 'INACTIVE')
397
+ * );
398
+ *
399
+ * // Delete with complex condition
400
+ * transaction.delete(
401
+ * 'products',
402
+ * { pk: 'PROD#ABC' },
403
+ * op => op.and([
404
+ * op.eq('status', 'DRAFT'),
405
+ * op.lt('version', 5)
406
+ * ])
407
+ * );
408
+ * ```
409
+ *
410
+ * @param tableName - The name of the DynamoDB table
411
+ * @param key - The primary key of the item to delete
412
+ * @param condition - Optional condition that must be satisfied
413
+ * @returns The transaction builder for method chaining
414
+ * @throws {Error} If a duplicate item is detected in the transaction
415
+ */
416
+ delete(tableName, key, condition) {
417
+ const keyCondition = this.createKeyForPrimaryIndex(key);
418
+ this.checkForDuplicateItem(tableName, keyCondition);
419
+ const transactionItem = {
420
+ type: "Delete",
421
+ params: {
422
+ tableName,
423
+ key: keyCondition
424
+ }
425
+ };
426
+ if (condition) {
427
+ const { expression, names, values } = prepareExpressionParams(condition);
428
+ transactionItem.params.conditionExpression = expression;
429
+ transactionItem.params.expressionAttributeNames = names;
430
+ transactionItem.params.expressionAttributeValues = values;
431
+ }
432
+ this.items.push(transactionItem);
433
+ return this;
434
+ }
435
+ /**
436
+ * Adds a pre-configured delete operation to the transaction.
437
+ * Use this method when you need to:
438
+ * - Reuse delete commands from DeleteBuilder
439
+ * - Add complex delete operations with pre-configured parameters
440
+ * - Integrate with existing delete command configurations
441
+ *
442
+ * This method is particularly useful when working with DeleteBuilder
443
+ * to maintain consistency in delete operations across your application.
444
+ *
445
+ * @example
446
+ * ```typescript
447
+ * // Create a delete command with DeleteBuilder
448
+ * const deleteCommand = new DeleteBuilder(executor, 'users', { pk: 'USER#123' })
449
+ * .condition(op => op.and([
450
+ * op.attributeExists('pk'),
451
+ * op.eq('status', 'INACTIVE')
452
+ * ]))
453
+ * .toDynamoCommand();
454
+ *
455
+ * // Add the command to the transaction
456
+ * transaction.deleteWithCommand(deleteCommand);
457
+ * ```
458
+ *
459
+ * @param command - The complete delete command configuration
460
+ * @returns The transaction builder for method chaining
461
+ * @throws {Error} If a duplicate item is detected in the transaction
462
+ * @see DeleteBuilder for creating delete commands
463
+ */
464
+ deleteWithCommand(command) {
465
+ let keyForDuplicateCheck;
466
+ let keyForTransaction;
467
+ if (typeof command.key === "object" && command.key !== null && "pk" in command.key) {
468
+ keyForTransaction = this.createKeyForPrimaryIndex(command.key);
469
+ keyForDuplicateCheck = keyForTransaction;
470
+ } else {
471
+ keyForTransaction = command.key;
472
+ keyForDuplicateCheck = command.key;
473
+ }
474
+ this.checkForDuplicateItem(command.tableName, keyForDuplicateCheck);
475
+ const transactionItem = {
476
+ type: "Delete",
477
+ params: {
478
+ ...command,
479
+ key: keyForTransaction
480
+ }
481
+ };
482
+ this.items.push(transactionItem);
483
+ return this;
484
+ }
485
+ /**
486
+ * Adds an update operation to the transaction.
487
+ * Use this method when you need to:
488
+ * - Modify existing items as part of a transaction
489
+ * - Update multiple attributes atomically
490
+ * - Apply conditional updates
491
+ * - Perform complex attribute manipulations
492
+ *
493
+ * The method supports all DynamoDB update expressions:
494
+ * - SET: Modify or add attributes
495
+ * - REMOVE: Delete attributes
496
+ * - ADD: Update numbers and sets
497
+ * - DELETE: Remove elements from a set
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * // Simple update
502
+ * transaction.update(
503
+ * 'orders',
504
+ * { pk: 'ORDER#123' },
505
+ * 'SET #status = :status',
506
+ * { '#status': 'status' },
507
+ * { ':status': 'PROCESSING' }
508
+ * );
509
+ *
510
+ * // Complex update with multiple operations
511
+ * transaction.update(
512
+ * 'products',
513
+ * { pk: 'PROD#ABC' },
514
+ * 'SET #qty = #qty - :amount, #status = :status REMOVE #oldAttr',
515
+ * { '#qty': 'quantity', '#status': 'status', '#oldAttr': 'deprecated_field' },
516
+ * { ':amount': 1, ':status': 'LOW_STOCK' }
517
+ * );
518
+ *
519
+ * // Conditional update
520
+ * transaction.update(
521
+ * 'users',
522
+ * { pk: 'USER#123' },
523
+ * 'SET #lastLogin = :now',
524
+ * { '#lastLogin': 'lastLoginDate' },
525
+ * { ':now': new Date().toISOString() },
526
+ * op => op.attributeExists('pk')
527
+ * );
528
+ * ```
529
+ *
530
+ * @param tableName - The name of the DynamoDB table
531
+ * @param key - The primary key of the item to update
532
+ * @param updateExpression - The update expression (SET, REMOVE, ADD, DELETE)
533
+ * @param expressionAttributeNames - Map of attribute name placeholders to actual names
534
+ * @param expressionAttributeValues - Map of value placeholders to actual values
535
+ * @param condition - Optional condition that must be satisfied
536
+ * @returns The transaction builder for method chaining
537
+ * @throws {Error} If a duplicate item is detected in the transaction
538
+ */
539
+ update(tableName, key, updateExpression, expressionAttributeNames, expressionAttributeValues, condition) {
540
+ const keyCondition = this.createKeyForPrimaryIndex(key);
541
+ this.checkForDuplicateItem(tableName, keyCondition);
542
+ const transactionItem = {
543
+ type: "Update",
544
+ params: {
545
+ tableName,
546
+ key: keyCondition,
547
+ updateExpression,
548
+ expressionAttributeNames,
549
+ expressionAttributeValues
550
+ }
551
+ };
552
+ if (condition) {
553
+ const { expression, names, values } = prepareExpressionParams(condition);
554
+ transactionItem.params.conditionExpression = expression;
555
+ transactionItem.params.expressionAttributeNames = {
556
+ ...transactionItem.params.expressionAttributeNames,
557
+ ...names
558
+ };
559
+ transactionItem.params.expressionAttributeValues = {
560
+ ...transactionItem.params.expressionAttributeValues,
561
+ ...values
562
+ };
563
+ }
564
+ this.items.push(transactionItem);
565
+ return this;
566
+ }
567
+ /**
568
+ * Adds a pre-configured update operation to the transaction.
569
+ * Use this method when you need to:
570
+ * - Reuse update commands from UpdateBuilder
571
+ * - Add complex update operations with pre-configured parameters
572
+ * - Integrate with existing update command configurations
573
+ *
574
+ * This method is particularly useful when working with UpdateBuilder
575
+ * to maintain consistency in update operations across your application.
576
+ *
577
+ * @example
578
+ * ```typescript
579
+ * // Create an update command with UpdateBuilder
580
+ * const updateCommand = new UpdateBuilder(executor, 'inventory', { pk: 'PROD#ABC' })
581
+ * .set('quantity', ':qty')
582
+ * .set('lastUpdated', ':now')
583
+ * .values({
584
+ * ':qty': 100,
585
+ * ':now': new Date().toISOString()
586
+ * })
587
+ * .condition(op => op.gt('quantity', 0))
588
+ * .toDynamoCommand();
589
+ *
590
+ * // Add the command to the transaction
591
+ * transaction.updateWithCommand(updateCommand);
592
+ * ```
593
+ *
594
+ * @param command - The complete update command configuration
595
+ * @returns The transaction builder for method chaining
596
+ * @throws {Error} If a duplicate item is detected in the transaction
597
+ * @see UpdateBuilder for creating update commands
598
+ */
599
+ updateWithCommand(command) {
600
+ let keyForDuplicateCheck;
601
+ let keyForTransaction;
602
+ if (typeof command.key === "object" && command.key !== null && "pk" in command.key) {
603
+ keyForTransaction = this.createKeyForPrimaryIndex(command.key);
604
+ keyForDuplicateCheck = keyForTransaction;
605
+ } else {
606
+ keyForTransaction = command.key;
607
+ keyForDuplicateCheck = command.key;
608
+ }
609
+ this.checkForDuplicateItem(command.tableName, keyForDuplicateCheck);
610
+ const transactionItem = {
611
+ type: "Update",
612
+ params: {
613
+ ...command,
614
+ key: keyForTransaction
615
+ }
616
+ };
617
+ this.items.push(transactionItem);
618
+ return this;
619
+ }
620
+ /**
621
+ * Adds a condition check operation to the transaction.
622
+ * Use this method when you need to:
623
+ * - Validate item state without modifying it
624
+ * - Ensure data consistency across tables
625
+ * - Implement complex business rules
626
+ * - Verify preconditions for other operations
627
+ *
628
+ * Condition checks are particularly useful for:
629
+ * - Implementing optimistic locking
630
+ * - Ensuring referential integrity
631
+ * - Validating business rules atomically
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * // Check if order is in correct state
636
+ * transaction.conditionCheck(
637
+ * 'orders',
638
+ * { pk: 'ORDER#123' },
639
+ * op => op.eq('status', 'PENDING')
640
+ * );
641
+ *
642
+ * // Complex condition check
643
+ * transaction.conditionCheck(
644
+ * 'inventory',
645
+ * { pk: 'PROD#ABC' },
646
+ * op => op.and([
647
+ * op.gt('quantity', 0),
648
+ * op.eq('status', 'ACTIVE'),
649
+ * op.attributeExists('lastRestockDate')
650
+ * ])
651
+ * );
652
+ *
653
+ * // Check with multiple attributes
654
+ * transaction.conditionCheck(
655
+ * 'users',
656
+ * { pk: 'USER#123' },
657
+ * op => op.or([
658
+ * op.eq('status', 'PREMIUM'),
659
+ * op.gte('credits', 100)
660
+ * ])
661
+ * );
662
+ * ```
663
+ *
664
+ * @param tableName - The name of the DynamoDB table
665
+ * @param key - The primary key of the item to check
666
+ * @param condition - The condition that must be satisfied
667
+ * @returns The transaction builder for method chaining
668
+ * @throws {Error} If a duplicate item is detected in the transaction
669
+ * @throws {Error} If condition expression generation fails
670
+ */
671
+ conditionCheck(tableName, key, condition) {
672
+ const keyCondition = this.createKeyForPrimaryIndex(key);
673
+ this.checkForDuplicateItem(tableName, keyCondition);
674
+ const { expression, names, values } = prepareExpressionParams(condition);
675
+ if (!expression) {
676
+ throw new Error("Failed to generate condition expression");
677
+ }
678
+ const transactionItem = {
679
+ type: "ConditionCheck",
680
+ params: {
681
+ tableName,
682
+ key: keyCondition,
683
+ conditionExpression: expression,
684
+ expressionAttributeNames: names,
685
+ expressionAttributeValues: values
686
+ }
687
+ };
688
+ this.items.push(transactionItem);
689
+ return this;
690
+ }
691
+ /**
692
+ * Adds a pre-configured condition check operation to the transaction.
693
+ * Use this method when you need to:
694
+ * - Reuse condition checks from ConditionCheckBuilder
695
+ * - Add complex condition checks with pre-configured parameters
696
+ * - Integrate with existing condition check configurations
697
+ *
698
+ * This method is particularly useful when working with ConditionCheckBuilder
699
+ * to maintain consistency in condition checks across your application.
700
+ *
701
+ * @example
702
+ * ```typescript
703
+ * // Create a condition check with ConditionCheckBuilder
704
+ * const checkCommand = new ConditionCheckBuilder('inventory', { pk: 'PROD#ABC' })
705
+ * .condition(op => op.and([
706
+ * op.between('quantity', 10, 100),
707
+ * op.beginsWith('category', 'ELECTRONICS'),
708
+ * op.attributeExists('lastAuditDate')
709
+ * ]))
710
+ * .toDynamoCommand();
711
+ *
712
+ * // Add the command to the transaction
713
+ * transaction.conditionCheckWithCommand(checkCommand);
714
+ * ```
715
+ *
716
+ * @param command - The complete condition check command configuration
717
+ * @returns The transaction builder for method chaining
718
+ * @throws {Error} If a duplicate item is detected in the transaction
719
+ * @see ConditionCheckBuilder for creating condition check commands
720
+ */
721
+ conditionCheckWithCommand(command) {
722
+ let keyForDuplicateCheck;
723
+ let keyForTransaction;
724
+ if (typeof command.key === "object" && command.key !== null && "pk" in command.key) {
725
+ keyForTransaction = this.createKeyForPrimaryIndex(command.key);
726
+ keyForDuplicateCheck = keyForTransaction;
727
+ } else {
728
+ keyForTransaction = command.key;
729
+ keyForDuplicateCheck = command.key;
730
+ }
731
+ this.checkForDuplicateItem(command.tableName, keyForDuplicateCheck);
732
+ const transactionItem = {
733
+ type: "ConditionCheck",
734
+ params: {
735
+ ...command,
736
+ key: keyForTransaction
737
+ }
738
+ };
739
+ this.items.push(transactionItem);
740
+ return this;
741
+ }
742
+ /**
743
+ * Sets options for the transaction execution.
744
+ * Use this method when you need to:
745
+ * - Enable idempotent transactions
746
+ * - Track consumed capacity
747
+ * - Monitor item collection metrics
748
+ *
749
+ * @example
750
+ * ```typescript
751
+ * // Enable idempotency and capacity tracking
752
+ * transaction.withOptions({
753
+ * clientRequestToken: 'unique-request-id-123',
754
+ * returnConsumedCapacity: 'TOTAL'
755
+ * });
756
+ *
757
+ * // Track item collection metrics
758
+ * transaction.withOptions({
759
+ * returnItemCollectionMetrics: 'SIZE'
760
+ * });
761
+ * ```
762
+ *
763
+ * Note: ClientRequestToken can be used to make transactions idempotent,
764
+ * ensuring the same transaction is not executed multiple times.
765
+ *
766
+ * @param options - Configuration options for the transaction
767
+ * @returns The transaction builder for method chaining
768
+ */
769
+ withOptions(options) {
770
+ this.options = { ...this.options, ...options };
771
+ return this;
772
+ }
773
+ /**
774
+ * Gets a human-readable representation of the transaction items.
775
+ * Use this method when you need to:
776
+ * - Debug complex transactions
777
+ * - Verify operation parameters
778
+ * - Log transaction details
779
+ * - Troubleshoot condition expressions
780
+ *
781
+ * The method resolves all expression placeholders with their actual values,
782
+ * making it easier to understand the transaction's operations.
783
+ *
784
+ * @example
785
+ * ```typescript
786
+ * // Add multiple operations
787
+ * transaction
788
+ * .put('orders', { orderId: '123', status: 'PENDING' })
789
+ * .update('inventory',
790
+ * { productId: 'ABC' },
791
+ * 'SET quantity = quantity - :amount',
792
+ * undefined,
793
+ * { ':amount': 1 }
794
+ * );
795
+ *
796
+ * // Debug the transaction
797
+ * const debugInfo = transaction.debug();
798
+ * console.log('Transaction operations:', debugInfo);
799
+ * ```
800
+ *
801
+ * @returns An array of readable representations of the transaction items
802
+ */
803
+ debug() {
804
+ return debugTransaction(this.items);
805
+ }
806
+ /**
807
+ * Executes all operations in the transaction atomically.
808
+ * Use this method when you need to:
809
+ * - Perform multiple operations atomically
810
+ * - Ensure all-or-nothing execution
811
+ * - Maintain data consistency across operations
812
+ *
813
+ * The transaction will only succeed if all operations succeed.
814
+ * If any operation fails, the entire transaction is rolled back.
815
+ *
816
+ * @example
817
+ * ```typescript
818
+ * try {
819
+ * // Build and execute transaction
820
+ * await transaction
821
+ * .put('orders', newOrder)
822
+ * .update('inventory',
823
+ * { productId: 'ABC' },
824
+ * 'SET quantity = quantity - :qty',
825
+ * undefined,
826
+ * { ':qty': 1 }
827
+ * )
828
+ * .conditionCheck('products',
829
+ * { productId: 'ABC' },
830
+ * op => op.eq('status', 'ACTIVE')
831
+ * )
832
+ * .execute();
833
+ *
834
+ * console.log('Transaction completed successfully');
835
+ * } catch (error) {
836
+ * // Handle transaction failure
837
+ * console.error('Transaction failed:', error);
838
+ * }
839
+ * ```
840
+ *
841
+ * @throws {Error} If no transaction items are specified
842
+ * @throws {Error} If any operation in the transaction fails
843
+ * @returns A promise that resolves when the transaction completes
844
+ */
845
+ async execute() {
846
+ if (this.items.length === 0) {
847
+ throw new Error("No transaction items specified");
848
+ }
849
+ const transactItems = this.items.map((item) => {
850
+ switch (item.type) {
851
+ case "Put":
852
+ return {
853
+ Put: {
854
+ TableName: item.params.tableName,
855
+ Item: item.params.item,
856
+ ConditionExpression: item.params.conditionExpression,
857
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
858
+ ExpressionAttributeValues: item.params.expressionAttributeValues
859
+ }
860
+ };
861
+ case "Delete":
862
+ return {
863
+ Delete: {
864
+ TableName: item.params.tableName,
865
+ Key: item.params.key,
866
+ ConditionExpression: item.params.conditionExpression,
867
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
868
+ ExpressionAttributeValues: item.params.expressionAttributeValues
869
+ }
870
+ };
871
+ case "Update":
872
+ return {
873
+ Update: {
874
+ TableName: item.params.tableName,
875
+ Key: item.params.key,
876
+ UpdateExpression: item.params.updateExpression,
877
+ ConditionExpression: item.params.conditionExpression,
878
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
879
+ ExpressionAttributeValues: item.params.expressionAttributeValues
880
+ }
881
+ };
882
+ case "ConditionCheck":
883
+ return {
884
+ ConditionCheck: {
885
+ TableName: item.params.tableName,
886
+ Key: item.params.key,
887
+ ConditionExpression: item.params.conditionExpression,
888
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
889
+ ExpressionAttributeValues: item.params.expressionAttributeValues
890
+ }
891
+ };
892
+ default: {
893
+ const exhaustiveCheck = item;
894
+ throw new Error(`Unsupported transaction item type: ${String(exhaustiveCheck)}`);
895
+ }
896
+ }
897
+ });
898
+ const params = {
899
+ TransactItems: transactItems,
900
+ ClientRequestToken: this.options.clientRequestToken,
901
+ ReturnConsumedCapacity: this.options.returnConsumedCapacity,
902
+ ReturnItemCollectionMetrics: this.options.returnItemCollectionMetrics
903
+ };
904
+ try {
905
+ await this.executor(params);
906
+ } catch (error) {
907
+ console.log(this.debug());
908
+ console.error("Error executing transaction:", error);
909
+ throw error;
910
+ }
911
+ }
912
+ };
913
+
914
+ export { TransactionBuilder };
915
+ //# sourceMappingURL=transaction-builder.js.map
916
+ //# sourceMappingURL=transaction-builder.js.map