dyno-table 0.1.7 → 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 +386 -338
  42. package/dist/index.js +247 -232
  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 +24 -26
package/dist/table.cjs ADDED
@@ -0,0 +1,3265 @@
1
+ 'use strict';
2
+
3
+ // src/conditions.ts
4
+ var createComparisonCondition = (type) => (attr, value) => ({
5
+ type,
6
+ attr,
7
+ value
8
+ });
9
+ var eq = createComparisonCondition("eq");
10
+ var ne = createComparisonCondition("ne");
11
+ var lt = createComparisonCondition("lt");
12
+ var lte = createComparisonCondition("lte");
13
+ var gt = createComparisonCondition("gt");
14
+ var gte = createComparisonCondition("gte");
15
+ var between = (attr, lower, upper) => ({
16
+ type: "between",
17
+ attr,
18
+ value: [lower, upper]
19
+ });
20
+ var beginsWith = createComparisonCondition("beginsWith");
21
+ var contains = createComparisonCondition("contains");
22
+ var attributeExists = (attr) => ({
23
+ type: "attributeExists",
24
+ attr
25
+ });
26
+ var attributeNotExists = (attr) => ({
27
+ type: "attributeNotExists",
28
+ attr
29
+ });
30
+ var and = (...conditions) => ({
31
+ type: "and",
32
+ conditions
33
+ });
34
+ var or = (...conditions) => ({
35
+ type: "or",
36
+ conditions
37
+ });
38
+ var not = (condition) => ({
39
+ type: "not",
40
+ condition
41
+ });
42
+
43
+ // src/expression.ts
44
+ var generateAttributeName = (params, attr) => {
45
+ for (const [existingName, existingAttr] of Object.entries(params.expressionAttributeNames)) {
46
+ if (existingAttr === attr) {
47
+ return existingName;
48
+ }
49
+ }
50
+ const attrName = `#${Object.keys(params.expressionAttributeNames).length}`;
51
+ params.expressionAttributeNames[attrName] = attr;
52
+ return attrName;
53
+ };
54
+ var generateValueName = (params, value) => {
55
+ const valueName = `:${params.valueCounter.count++}`;
56
+ params.expressionAttributeValues[valueName] = value;
57
+ return valueName;
58
+ };
59
+ var validateCondition = (condition, requiresAttr = true, requiresValue = true) => {
60
+ if (requiresAttr && !condition.attr) {
61
+ throw new Error(`Attribute is required for ${condition.type} condition`);
62
+ }
63
+ if (requiresValue && condition.value === void 0) {
64
+ throw new Error(`Value is required for ${condition.type} condition`);
65
+ }
66
+ };
67
+ var buildComparisonExpression = (condition, operator, params) => {
68
+ validateCondition(condition);
69
+ if (!condition.attr) {
70
+ throw new Error(`Attribute is required for ${condition.type} condition`);
71
+ }
72
+ const attrName = generateAttributeName(params, condition.attr);
73
+ const valueName = generateValueName(params, condition.value);
74
+ return `${attrName} ${operator} ${valueName}`;
75
+ };
76
+ var buildBetweenExpression = (condition, params) => {
77
+ validateCondition(condition);
78
+ if (!condition.attr) {
79
+ throw new Error(`Attribute is required for ${condition.type} condition`);
80
+ }
81
+ if (!Array.isArray(condition.value) || condition.value.length !== 2) {
82
+ throw new Error("Between condition requires an array of two values");
83
+ }
84
+ const attrName = generateAttributeName(params, condition.attr);
85
+ const lowerName = generateValueName(params, condition.value[0]);
86
+ const upperName = generateValueName(params, condition.value[1]);
87
+ return `${attrName} BETWEEN ${lowerName} AND ${upperName}`;
88
+ };
89
+ var buildFunctionExpression = (functionName, condition, params) => {
90
+ validateCondition(condition);
91
+ if (!condition.attr) {
92
+ throw new Error(`Attribute is required for ${condition.type} condition`);
93
+ }
94
+ const attrName = generateAttributeName(params, condition.attr);
95
+ const valueName = generateValueName(params, condition.value);
96
+ return `${functionName}(${attrName}, ${valueName})`;
97
+ };
98
+ var buildAttributeFunction = (functionName, condition, params) => {
99
+ validateCondition(condition, true, false);
100
+ if (!condition.attr) {
101
+ throw new Error(`Attribute is required for ${condition.type} condition`);
102
+ }
103
+ const attrName = generateAttributeName(params, condition.attr);
104
+ return `${functionName}(${attrName})`;
105
+ };
106
+ var buildLogicalExpression = (operator, conditions, params) => {
107
+ if (!conditions || conditions.length === 0) {
108
+ throw new Error(`At least one condition is required for ${operator} expression`);
109
+ }
110
+ const expressions = conditions.map((c) => buildExpression(c, params));
111
+ return `(${expressions.join(` ${operator} `)})`;
112
+ };
113
+ var buildExpression = (condition, params) => {
114
+ if (!condition) return "";
115
+ try {
116
+ const expressionBuilders = {
117
+ eq: () => buildComparisonExpression(condition, "=", params),
118
+ ne: () => buildComparisonExpression(condition, "<>", params),
119
+ lt: () => buildComparisonExpression(condition, "<", params),
120
+ lte: () => buildComparisonExpression(condition, "<=", params),
121
+ gt: () => buildComparisonExpression(condition, ">", params),
122
+ gte: () => buildComparisonExpression(condition, ">=", params),
123
+ between: () => buildBetweenExpression(condition, params),
124
+ beginsWith: () => buildFunctionExpression("begins_with", condition, params),
125
+ contains: () => buildFunctionExpression("contains", condition, params),
126
+ attributeExists: () => buildAttributeFunction("attribute_exists", condition, params),
127
+ attributeNotExists: () => buildAttributeFunction("attribute_not_exists", condition, params),
128
+ and: () => {
129
+ if (!condition.conditions) {
130
+ throw new Error("Conditions array is required for AND operator");
131
+ }
132
+ return buildLogicalExpression("AND", condition.conditions, params);
133
+ },
134
+ or: () => {
135
+ if (!condition.conditions) {
136
+ throw new Error("Conditions array is required for OR operator");
137
+ }
138
+ return buildLogicalExpression("OR", condition.conditions, params);
139
+ },
140
+ not: () => {
141
+ if (!condition.condition) {
142
+ throw new Error("Condition is required for NOT operator");
143
+ }
144
+ return `NOT (${buildExpression(condition.condition, params)})`;
145
+ }
146
+ };
147
+ const builder = expressionBuilders[condition.type];
148
+ if (!builder) {
149
+ throw new Error(`Unknown condition type: ${condition.type}`);
150
+ }
151
+ return builder();
152
+ } catch (error) {
153
+ if (error instanceof Error) {
154
+ console.error(`Error building expression for condition type ${condition.type}:`, error.message);
155
+ } else {
156
+ console.error(`Error building expression for condition type ${condition.type}:`, error);
157
+ }
158
+ throw error;
159
+ }
160
+ };
161
+ var prepareExpressionParams = (condition) => {
162
+ if (!condition) return {};
163
+ const params = {
164
+ expressionAttributeNames: {},
165
+ expressionAttributeValues: {},
166
+ valueCounter: { count: 0 }
167
+ };
168
+ const expression = buildExpression(condition, params);
169
+ return {
170
+ expression,
171
+ names: Object.keys(params.expressionAttributeNames).length > 0 ? params.expressionAttributeNames : void 0,
172
+ values: Object.keys(params.expressionAttributeValues).length > 0 ? params.expressionAttributeValues : void 0
173
+ };
174
+ };
175
+
176
+ // src/builders/paginator.ts
177
+ var Paginator = class {
178
+ queryBuilder;
179
+ pageSize;
180
+ currentPage = 0;
181
+ lastEvaluatedKey;
182
+ hasMorePages = true;
183
+ totalItemsRetrieved = 0;
184
+ overallLimit;
185
+ constructor(queryBuilder, pageSize) {
186
+ this.queryBuilder = queryBuilder;
187
+ this.pageSize = pageSize;
188
+ this.overallLimit = queryBuilder.getLimit();
189
+ }
190
+ /**
191
+ * Gets the current page number (1-indexed).
192
+ * Use this method when you need to:
193
+ * - Track progress through dinosaur lists
194
+ * - Display habitat inspection status
195
+ * - Monitor security sweep progress
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * const paginator = new QueryBuilder(executor, eq('species', 'Tyrannosaurus'))
200
+ * .paginate(5);
201
+ *
202
+ * await paginator.getNextPage();
203
+ * console.log(`Reviewing T-Rex group ${paginator.getCurrentPage()}`);
204
+ * ```
205
+ *
206
+ * @returns The current page number, starting from 1
207
+ */
208
+ getCurrentPage() {
209
+ return this.currentPage;
210
+ }
211
+ /**
212
+ * Checks if there are more pages of dinosaurs or habitats to process.
213
+ * Use this method when you need to:
214
+ * - Check for more dinosaurs to review
215
+ * - Continue habitat inspections
216
+ * - Process security incidents
217
+ * - Complete feeding schedules
218
+ *
219
+ * This method takes into account both:
220
+ * - DynamoDB's lastEvaluatedKey mechanism
221
+ * - Any overall limit set on the query
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * // Process all security incidents
226
+ * const paginator = new QueryBuilder(executor, eq('type', 'SECURITY_BREACH'))
227
+ * .sortDescending()
228
+ * .paginate(10);
229
+ *
230
+ * while (paginator.hasNextPage()) {
231
+ * const page = await paginator.getNextPage();
232
+ * for (const incident of page.items) {
233
+ * await processSecurityBreach(incident);
234
+ * }
235
+ * console.log(`Processed incidents page ${page.page}`);
236
+ * }
237
+ * ```
238
+ *
239
+ * @returns true if there are more pages available, false otherwise
240
+ */
241
+ hasNextPage() {
242
+ if (this.overallLimit !== void 0 && this.totalItemsRetrieved >= this.overallLimit) {
243
+ return false;
244
+ }
245
+ return this.hasMorePages;
246
+ }
247
+ /**
248
+ * Retrieves the next page of dinosaurs or habitats from DynamoDB.
249
+ * Use this method when you need to:
250
+ * - Process dinosaur groups systematically
251
+ * - Review habitat inspections in batches
252
+ * - Monitor security incidents in sequence
253
+ * - Schedule feeding rotations
254
+ *
255
+ * This method handles:
256
+ * - Automatic continuation between groups
257
+ * - Respect for park capacity limits
258
+ * - Group size adjustments for safety
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * const paginator = new QueryBuilder(executor, eq('species', 'Velociraptor'))
263
+ * .filter(op => op.eq('status', 'ACTIVE'))
264
+ * .paginate(5);
265
+ *
266
+ * // Check first raptor group
267
+ * const page1 = await paginator.getNextPage();
268
+ * console.log(`Found ${page1.items.length} active raptors`);
269
+ *
270
+ * // Continue inspection if more groups exist
271
+ * if (page1.hasNextPage) {
272
+ * const page2 = await paginator.getNextPage();
273
+ * console.log(`Inspecting raptor group ${page2.page}`);
274
+ *
275
+ * for (const raptor of page2.items) {
276
+ * await performHealthCheck(raptor);
277
+ * }
278
+ * }
279
+ * ```
280
+ *
281
+ * @returns A promise that resolves to a PaginationResult containing:
282
+ * - items: The dinosaurs/habitats for this page
283
+ * - hasNextPage: Whether more groups exist
284
+ * - page: The current group number
285
+ * - lastEvaluatedKey: DynamoDB's continuation token
286
+ */
287
+ async getNextPage() {
288
+ if (!this.hasNextPage()) {
289
+ return {
290
+ items: [],
291
+ hasNextPage: false,
292
+ page: this.currentPage
293
+ };
294
+ }
295
+ let effectivePageSize = this.pageSize;
296
+ if (this.overallLimit !== void 0) {
297
+ const remainingItems = this.overallLimit - this.totalItemsRetrieved;
298
+ if (remainingItems <= 0) {
299
+ return {
300
+ items: [],
301
+ hasNextPage: false,
302
+ page: this.currentPage
303
+ };
304
+ }
305
+ effectivePageSize = Math.min(effectivePageSize, remainingItems);
306
+ }
307
+ const query = this.queryBuilder.clone().limit(effectivePageSize);
308
+ if (this.lastEvaluatedKey) {
309
+ query.startFrom(this.lastEvaluatedKey);
310
+ }
311
+ const result = await query.execute();
312
+ this.currentPage += 1;
313
+ this.lastEvaluatedKey = result.lastEvaluatedKey;
314
+ this.totalItemsRetrieved += result.items.length;
315
+ this.hasMorePages = !!result.lastEvaluatedKey && (this.overallLimit === void 0 || this.totalItemsRetrieved < this.overallLimit);
316
+ return {
317
+ items: result.items,
318
+ lastEvaluatedKey: result.lastEvaluatedKey,
319
+ hasNextPage: this.hasNextPage(),
320
+ page: this.currentPage
321
+ };
322
+ }
323
+ /**
324
+ * Gets all remaining dinosaurs or habitats and combines them into a single array.
325
+ * Use this method when you need to:
326
+ * - Generate complete park inventory
327
+ * - Perform full security audit
328
+ * - Create comprehensive feeding schedule
329
+ * - Run park-wide health checks
330
+ *
331
+ * Note: Use with caution! This method:
332
+ * - Could overwhelm systems with large dinosaur populations
333
+ * - Makes multiple database requests
334
+ * - May cause system strain during peak hours
335
+ *
336
+ * @example
337
+ * ```ts
338
+ * // Get complete carnivore inventory
339
+ * const paginator = new QueryBuilder(executor, eq('diet', 'CARNIVORE'))
340
+ * .filter(op => op.eq('status', 'ACTIVE'))
341
+ * .paginate(10);
342
+ *
343
+ * try {
344
+ * const allCarnivores = await paginator.getAllPages();
345
+ * console.log(`Park contains ${allCarnivores.length} active carnivores`);
346
+ *
347
+ * // Calculate total threat level
348
+ * const totalThreat = allCarnivores.reduce(
349
+ * (sum, dino) => sum + dino.stats.threatLevel,
350
+ * 0
351
+ * );
352
+ * console.log(`Total threat level: ${totalThreat}`);
353
+ * } catch (error) {
354
+ * console.error('Failed to complete carnivore census:', error);
355
+ * }
356
+ * ```
357
+ *
358
+ * @returns A promise that resolves to an array containing all remaining items
359
+ */
360
+ async getAllPages() {
361
+ const allItems = [];
362
+ while (this.hasNextPage()) {
363
+ const result = await this.getNextPage();
364
+ allItems.push(...result.items);
365
+ }
366
+ return allItems;
367
+ }
368
+ };
369
+
370
+ // src/builders/filter-builder.ts
371
+ var FilterBuilder = class {
372
+ options = {};
373
+ selectedFields = /* @__PURE__ */ new Set();
374
+ /**
375
+ * Sets the maximum number of items to return.
376
+ * Use this method when you need to:
377
+ * - Limit the number of dinosaurs returned
378
+ * - Control the size of habitat reports
379
+ * - Implement manual pagination of security logs
380
+ *
381
+ * Note: This limit applies to the items that match the key condition
382
+ * before any filter expressions are applied.
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * // Get first 10 dinosaurs
387
+ * const result = await builder
388
+ * .limit(10)
389
+ * .execute();
390
+ * ```
391
+ *
392
+ * @param limit - Maximum number of items to return
393
+ * @returns The builder instance for method chaining
394
+ */
395
+ limit(limit) {
396
+ this.options.limit = limit;
397
+ return this;
398
+ }
399
+ /**
400
+ * Gets the current limit set on the operation.
401
+ * This is used internally by the paginator to manage result sets.
402
+ *
403
+ * @returns The current limit or undefined if no limit is set
404
+ */
405
+ getLimit() {
406
+ return this.options.limit;
407
+ }
408
+ /**
409
+ * Specifies a Global Secondary Index (GSI) to use for the operation.
410
+ * Use this method when you need to:
411
+ * - Find dinosaurs by species or status
412
+ * - Search habitats by security level
413
+ * - Find incidents by date
414
+ * - List feeding schedules by time
415
+ *
416
+ * @example
417
+ * ```typescript
418
+ * // Find all dinosaurs of a specific species
419
+ * builder
420
+ * .useIndex('species-status-index')
421
+ * .filter(op => op.eq('status', 'ACTIVE'));
422
+ *
423
+ * // Search high-security habitats
424
+ * builder
425
+ * .useIndex('security-level-index')
426
+ * .filter(op =>
427
+ * op.and([
428
+ * op.gt('securityLevel', 8),
429
+ * op.eq('status', 'OPERATIONAL')
430
+ * ])
431
+ * );
432
+ * ```
433
+ *
434
+ * @param indexName - The name of the GSI to use (type-safe based on table configuration)
435
+ * @returns The builder instance for method chaining
436
+ */
437
+ useIndex(indexName) {
438
+ this.options.indexName = indexName;
439
+ return this;
440
+ }
441
+ /**
442
+ * Sets whether to use strongly consistent reads for the operation.
443
+ * Use this method when you need to:
444
+ * - Get real-time dinosaur status updates
445
+ * - Monitor critical security systems
446
+ * - Track immediate habitat changes
447
+ * - Verify containment protocols
448
+ *
449
+ * Note:
450
+ * - Consistent reads are not available on GSIs
451
+ * - Consistent reads consume twice the throughput
452
+ * - Default is eventually consistent reads
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * // Check immediate dinosaur status
457
+ * const result = await builder
458
+ * .filter(op => op.eq('status', 'ACTIVE'))
459
+ * .consistentRead()
460
+ * .execute();
461
+ *
462
+ * // Monitor security breaches
463
+ * const result = await builder
464
+ * .useIndex('primary-index')
465
+ * .consistentRead(isEmergencyMode)
466
+ * .execute();
467
+ * ```
468
+ *
469
+ * @param consistentRead - Whether to use consistent reads (defaults to true)
470
+ * @returns The builder instance for method chaining
471
+ */
472
+ consistentRead(consistentRead = true) {
473
+ this.options.consistentRead = consistentRead;
474
+ return this;
475
+ }
476
+ /**
477
+ * Adds a filter expression to refine the operation results.
478
+ * Use this method when you need to:
479
+ * - Filter dinosaurs by behavior patterns
480
+ * - Find habitats with specific conditions
481
+ * - Search for security incidents
482
+ * - Monitor feeding patterns
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * // Find aggressive carnivores
487
+ * builder.filter(op =>
488
+ * op.and([
489
+ * op.eq('diet', 'CARNIVORE'),
490
+ * op.gt('aggressionLevel', 7),
491
+ * op.eq('status', 'ACTIVE')
492
+ * ])
493
+ * );
494
+ *
495
+ * // Search suitable breeding habitats
496
+ * builder.filter(op =>
497
+ * op.and([
498
+ * op.between('temperature', 25, 30),
499
+ * op.lt('currentOccupants', 3),
500
+ * op.eq('quarantineStatus', 'CLEAR')
501
+ * ])
502
+ * );
503
+ * ```
504
+ *
505
+ * @param condition - Either a Condition object or a callback function that builds the condition
506
+ * @returns The builder instance for method chaining
507
+ */
508
+ filter(condition) {
509
+ if (typeof condition === "function") {
510
+ const conditionOperator = {
511
+ eq,
512
+ ne,
513
+ lt,
514
+ lte,
515
+ gt,
516
+ gte,
517
+ between,
518
+ beginsWith,
519
+ contains,
520
+ attributeExists,
521
+ attributeNotExists,
522
+ and,
523
+ or,
524
+ not
525
+ };
526
+ this.options.filter = condition(conditionOperator);
527
+ } else {
528
+ this.options.filter = condition;
529
+ }
530
+ return this;
531
+ }
532
+ /**
533
+ * Specifies which attributes to return in the results.
534
+ * Use this method when you need to:
535
+ * - Get specific dinosaur attributes
536
+ * - Retrieve habitat statistics
537
+ * - Monitor security metrics
538
+ * - Optimize response size
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * // Get basic dinosaur info
543
+ * builder.select([
544
+ * 'species',
545
+ * 'status',
546
+ * 'stats.health',
547
+ * 'stats.aggressionLevel'
548
+ * ]);
549
+ *
550
+ * // Monitor habitat conditions
551
+ * builder
552
+ * .select('securityStatus')
553
+ * .select([
554
+ * 'currentOccupants',
555
+ * 'temperature',
556
+ * 'lastInspectionDate'
557
+ * ]);
558
+ * ```
559
+ *
560
+ * @param fields - A single field name or an array of field names to return
561
+ * @returns The builder instance for method chaining
562
+ */
563
+ select(fields) {
564
+ if (typeof fields === "string") {
565
+ this.selectedFields.add(fields);
566
+ } else if (Array.isArray(fields)) {
567
+ for (const field of fields) {
568
+ this.selectedFields.add(field);
569
+ }
570
+ }
571
+ this.options.projection = Array.from(this.selectedFields);
572
+ return this;
573
+ }
574
+ /**
575
+ * Creates a paginator that handles DynamoDB pagination automatically.
576
+ * The paginator handles:
577
+ * - Tracking the last evaluated key
578
+ * - Managing page boundaries
579
+ * - Respecting overall query limits
580
+ *
581
+ * @example
582
+ * ```typescript
583
+ * // Create a paginator for dinosaur records
584
+ * const paginator = builder
585
+ * .filter(op => op.eq('status', 'ACTIVE'))
586
+ * .paginate(10);
587
+ *
588
+ * // Process pages of dinosaur results
589
+ * while (paginator.hasNextPage()) {
590
+ * const page = await paginator.getNextPage();
591
+ * console.log(`Processing page ${page.page}, count: ${page.items.length}`);
592
+ * // Process dinosaur data
593
+ * }
594
+ * ```
595
+ *
596
+ * @param pageSize - The number of items to return per page
597
+ * @returns A Paginator instance that manages the pagination state
598
+ * @see Paginator for more pagination control options
599
+ */
600
+ paginate(pageSize) {
601
+ return new Paginator(this, pageSize);
602
+ }
603
+ /**
604
+ * Sets the starting point using a previous lastEvaluatedKey.
605
+ * Use this method when you need to:
606
+ * - Implement manual dinosaur list pagination
607
+ * - Resume habitat inspection reviews
608
+ * - Continue security incident analysis
609
+ * - Store operation position between sessions
610
+ *
611
+ * Note: This method is typically used for manual pagination.
612
+ * For automatic pagination, use the paginate() method instead.
613
+ *
614
+ * @example
615
+ * ```typescript
616
+ * // First batch of dinosaurs
617
+ * const result1 = await builder
618
+ * .filter(op => op.eq('status', 'ACTIVE'))
619
+ * .limit(5)
620
+ * .execute();
621
+ *
622
+ * if (result1.lastEvaluatedKey) {
623
+ * // Continue listing dinosaurs
624
+ * const result2 = await builder
625
+ * .filter(op => op.eq('status', 'ACTIVE'))
626
+ * .startFrom(result1.lastEvaluatedKey)
627
+ * .limit(5)
628
+ * .execute();
629
+ *
630
+ * console.log('Additional dinosaurs:', result2.items);
631
+ * }
632
+ * ```
633
+ *
634
+ * @param lastEvaluatedKey - The exclusive start key from a previous result
635
+ * @returns The builder instance for method chaining
636
+ */
637
+ startFrom(lastEvaluatedKey) {
638
+ this.options.lastEvaluatedKey = lastEvaluatedKey;
639
+ return this;
640
+ }
641
+ };
642
+
643
+ // src/builders/query-builder.ts
644
+ var QueryBuilder = class _QueryBuilder extends FilterBuilder {
645
+ keyCondition;
646
+ options = {};
647
+ executor;
648
+ constructor(executor, keyCondition) {
649
+ super();
650
+ this.executor = executor;
651
+ this.keyCondition = keyCondition;
652
+ }
653
+ /**
654
+ * Sets the maximum number of items to return from the query.
655
+ *
656
+ * Note: This is the default behavior if no sort order is specified.
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * // Get orders in chronological order
661
+ * const result = await new QueryBuilder(executor, eq('userId', '123'))
662
+ * .sortAscending()
663
+ * .execute();
664
+ *
665
+ * // Get events from oldest to newest
666
+ * const result = await new QueryBuilder(executor, eq('entityId', 'order-123'))
667
+ * .useIndex('entity-timestamp-index')
668
+ * .sortAscending()
669
+ * .execute();
670
+ * ```
671
+ *
672
+ * @returns The builder instance for method chaining
673
+ */
674
+ /**
675
+ * Sets the query to return items in ascending order by sort key.
676
+ *
677
+ * @example
678
+ * ```typescript
679
+ * // List dinosaurs by age
680
+ * const result = await new QueryBuilder(executor, eq('species', 'Velociraptor'))
681
+ * .useIndex('age-index')
682
+ * .sortAscending()
683
+ * .execute();
684
+ *
685
+ * // View incidents chronologically
686
+ * const result = await new QueryBuilder(executor, eq('type', 'SECURITY_BREACH'))
687
+ * .useIndex('date-index')
688
+ * .sortAscending()
689
+ * .execute();
690
+ * ```
691
+ *
692
+ * @returns The builder instance for method chaining
693
+ */
694
+ sortAscending() {
695
+ this.options.scanIndexForward = true;
696
+ return this;
697
+ }
698
+ /**
699
+ * Sets the query to return items in descending order by sort key.
700
+ *
701
+ * @example
702
+ * ```typescript
703
+ * // Get most recent security incidents
704
+ * const result = await new QueryBuilder(executor, eq('type', 'SECURITY_BREACH'))
705
+ * .useIndex('date-index')
706
+ * .sortDescending()
707
+ * .limit(10)
708
+ * .execute();
709
+ *
710
+ * // Check latest dinosaur activities
711
+ * const result = await new QueryBuilder(executor, eq('species', 'Velociraptor'))
712
+ * .useIndex('activity-time-index')
713
+ * .filter(op => op.eq('status', 'ACTIVE'))
714
+ * .sortDescending()
715
+ * .execute();
716
+ * ```
717
+ *
718
+ * @returns The builder instance for method chaining
719
+ */
720
+ sortDescending() {
721
+ this.options.scanIndexForward = false;
722
+ return this;
723
+ }
724
+ /**
725
+ * Creates a deep clone of this QueryBuilder instance.
726
+ *
727
+ * This is particularly useful when:
728
+ * - Implementing pagination (used internally by paginate())
729
+ * - Creating query templates
730
+ * - Running multiple variations of a query
731
+ *
732
+ * @example
733
+ * ```typescript
734
+ * // Create base dinosaur query
735
+ * const baseQuery = new QueryBuilder(executor, eq('species', 'Velociraptor'))
736
+ * .useIndex('status-index')
737
+ * .select(['id', 'status', 'location']);
738
+ *
739
+ * // Check active dinosaurs
740
+ * const activeRaptors = baseQuery.clone()
741
+ * .filter(op => op.eq('status', 'HUNTING'))
742
+ * .execute();
743
+ *
744
+ * // Check contained dinosaurs
745
+ * const containedRaptors = baseQuery.clone()
746
+ * .filter(op => op.eq('status', 'CONTAINED'))
747
+ * .execute();
748
+ *
749
+ * // Check sedated dinosaurs
750
+ * const sedatedRaptors = baseQuery.clone()
751
+ * .filter(op => op.eq('status', 'SEDATED'))
752
+ * .execute();
753
+ * ```
754
+ *
755
+ * @returns A new QueryBuilder instance with the same configuration
756
+ */
757
+ clone() {
758
+ const clone = new _QueryBuilder(this.executor, this.keyCondition);
759
+ clone.options = { ...this.options };
760
+ clone.selectedFields = new Set(this.selectedFields);
761
+ return clone;
762
+ }
763
+ /**
764
+ * Executes the query against DynamoDB.
765
+ *
766
+ * The method returns both the matched items and, if there are more results,
767
+ * a lastEvaluatedKey that can be used with startFrom() to continue the query.
768
+ *
769
+ * @example
770
+ * ```typescript
771
+ * try {
772
+ * // Find active carnivores in specific habitat
773
+ * const result = await new QueryBuilder(executor, eq('habitatId', 'PADDOCK-A'))
774
+ * .useIndex('species-status-index')
775
+ * .filter(op =>
776
+ * op.and([
777
+ * op.eq('diet', 'CARNIVORE'),
778
+ * op.eq('status', 'ACTIVE'),
779
+ * op.gt('aggressionLevel', 7)
780
+ * ])
781
+ * )
782
+ * .sortDescending()
783
+ * .limit(5)
784
+ * .execute();
785
+ *
786
+ * console.log(`Found ${result.items.length} dangerous dinosaurs`);
787
+ *
788
+ * if (result.lastEvaluatedKey) {
789
+ * console.log('Additional threats detected');
790
+ * }
791
+ * } catch (error) {
792
+ * console.error('Security scan failed:', error);
793
+ * }
794
+ * ```
795
+ *
796
+ * @returns A promise that resolves to an object containing:
797
+ * - items: Array of items matching the query
798
+ * - lastEvaluatedKey: Token for continuing the query, if more items exist
799
+ */
800
+ async execute() {
801
+ return this.executor(this.keyCondition, this.options);
802
+ }
803
+ };
804
+
805
+ // src/utils/debug-expression.ts
806
+ function debugCommand(command) {
807
+ const result = {};
808
+ function replaceAliases(expressionString) {
809
+ if (!expressionString) {
810
+ return expressionString;
811
+ }
812
+ let replacedString = expressionString;
813
+ for (const alias in command.expressionAttributeNames) {
814
+ const attributeName = command.expressionAttributeNames[alias];
815
+ const regex = new RegExp(alias, "g");
816
+ replacedString = replacedString.replace(regex, attributeName);
817
+ }
818
+ for (const alias in command.expressionAttributeValues) {
819
+ let attributeValue = command.expressionAttributeValues[alias];
820
+ if (attributeValue instanceof Set) {
821
+ const array = Array.from(attributeValue);
822
+ attributeValue = `Set(${array.length}){${array.map((v) => JSON.stringify(v)).join(", ")}}`;
823
+ } else {
824
+ attributeValue = JSON.stringify(attributeValue);
825
+ }
826
+ const regex = new RegExp(alias, "g");
827
+ replacedString = replacedString.replace(regex, attributeValue);
828
+ }
829
+ return replacedString;
830
+ }
831
+ if (command.updateExpression) {
832
+ result.updateExpression = replaceAliases(command.updateExpression);
833
+ }
834
+ if (command.conditionExpression) {
835
+ result.conditionExpression = replaceAliases(command.conditionExpression);
836
+ }
837
+ if (command.filterExpression) {
838
+ result.filterExpression = replaceAliases(command.filterExpression);
839
+ }
840
+ if (command.keyConditionExpression) {
841
+ result.keyConditionExpression = replaceAliases(command.keyConditionExpression);
842
+ }
843
+ if (command.projectionExpression) {
844
+ result.projectionExpression = replaceAliases(command.projectionExpression);
845
+ }
846
+ return {
847
+ raw: command,
848
+ readable: result
849
+ };
850
+ }
851
+
852
+ // src/builders/put-builder.ts
853
+ var PutBuilder = class {
854
+ item;
855
+ options;
856
+ executor;
857
+ tableName;
858
+ constructor(executor, item, tableName) {
859
+ this.executor = executor;
860
+ this.item = item;
861
+ this.tableName = tableName;
862
+ this.options = {
863
+ returnValues: "NONE"
864
+ };
865
+ }
866
+ /**
867
+ * Adds a condition that must be satisfied for the put operation to succeed.
868
+ * Use this method when you need to:
869
+ * - Prevent overwriting existing items (optimistic locking)
870
+ * - Ensure items meet certain criteria before replacement
871
+ * - Implement complex business rules for item updates
872
+ *
873
+ * @example
874
+ * ```ts
875
+ * // Ensure item doesn't exist (insert only)
876
+ * builder.condition(op => op.attributeNotExists('id'))
877
+ *
878
+ * // Complex condition with version check
879
+ * builder.condition(op =>
880
+ * op.and([
881
+ * op.attributeExists('id'),
882
+ * op.eq('version', currentVersion),
883
+ * op.eq('status', 'ACTIVE')
884
+ * ])
885
+ * )
886
+ * ```
887
+ *
888
+ * @param condition - Either a Condition object or a callback function that builds the condition
889
+ * @returns The builder instance for method chaining
890
+ */
891
+ /**
892
+ * Adds a condition that must be satisfied for the put operation to succeed.
893
+ *
894
+ * @example
895
+ * ```typescript
896
+ * // Ensure unique dinosaur ID
897
+ * builder.condition(op =>
898
+ * op.attributeNotExists('id')
899
+ * );
900
+ *
901
+ * // Verify habitat requirements
902
+ * builder.condition(op =>
903
+ * op.and([
904
+ * op.eq('securityStatus', 'READY'),
905
+ * op.attributeExists('lastInspection'),
906
+ * op.gt('securityLevel', 5)
907
+ * ])
908
+ * );
909
+ *
910
+ * // Check breeding facility conditions
911
+ * builder.condition(op =>
912
+ * op.and([
913
+ * op.between('temperature', 25, 30),
914
+ * op.between('humidity', 60, 80),
915
+ * op.eq('quarantineStatus', 'CLEAR')
916
+ * ])
917
+ * );
918
+ * ```
919
+ *
920
+ * @param condition - Either a Condition object or a callback function that builds the condition
921
+ * @returns The builder instance for method chaining
922
+ */
923
+ condition(condition) {
924
+ if (typeof condition === "function") {
925
+ const conditionOperator = {
926
+ eq,
927
+ ne,
928
+ lt,
929
+ lte,
930
+ gt,
931
+ gte,
932
+ between,
933
+ beginsWith,
934
+ contains,
935
+ attributeExists,
936
+ attributeNotExists,
937
+ and,
938
+ or,
939
+ not
940
+ };
941
+ this.options.condition = condition(conditionOperator);
942
+ } else {
943
+ this.options.condition = condition;
944
+ }
945
+ return this;
946
+ }
947
+ /**
948
+ * Sets whether to return the item's previous values (if it existed).
949
+ *
950
+ * @options
951
+ * - NONE: No return value
952
+ * - ALL_OLD: Returns the item's previous state if it existed, no read capacity units are consumed
953
+ * - CONSISTENT: (default) Performs a GET operation after the put to retrieve the item's new state
954
+ *
955
+ * @example
956
+ * ```ts
957
+ * // Get previous dinosaur state
958
+ * const result = await builder
959
+ * .returnValues('ALL_OLD')
960
+ * .execute();
961
+ *
962
+ * if (result) {
963
+ * console.log('Previous profile:', {
964
+ * species: result.species,
965
+ * status: result.status,
966
+ * stats: {
967
+ * health: result.stats.health,
968
+ * threatLevel: result.stats.threatLevel
969
+ * }
970
+ * });
971
+ * }
972
+ * ```
973
+ *
974
+ * @param returnValues - Use 'ALL_OLD' to return previous values if the item was overwritten, or 'NONE' (default).
975
+ * @returns The builder instance for method chaining
976
+ */
977
+ returnValues(returnValues) {
978
+ this.options.returnValues = returnValues;
979
+ return this;
980
+ }
981
+ /**
982
+ * Generate the DynamoDB command parameters
983
+ */
984
+ toDynamoCommand() {
985
+ const { expression, names, values } = prepareExpressionParams(this.options.condition);
986
+ return {
987
+ tableName: this.tableName,
988
+ item: this.item,
989
+ conditionExpression: expression,
990
+ expressionAttributeNames: names,
991
+ expressionAttributeValues: values,
992
+ returnValues: this.options.returnValues
993
+ };
994
+ }
995
+ /**
996
+ * Adds this put operation to a transaction.
997
+ * Use this method when you need to:
998
+ * - Transfer dinosaurs between habitats
999
+ * - Initialize new breeding programs
1000
+ * - Update multiple facility records
1001
+ *
1002
+ * @example
1003
+ * ```ts
1004
+ * const transaction = new TransactionBuilder();
1005
+ *
1006
+ * // Add dinosaur to new habitat
1007
+ * new PutBuilder(executor, {
1008
+ * id: 'TREX-002',
1009
+ * location: 'PADDOCK-B',
1010
+ * status: 'ACTIVE',
1011
+ * transferDate: new Date().toISOString()
1012
+ * }, 'dinosaurs')
1013
+ * .withTransaction(transaction);
1014
+ *
1015
+ * // Update habitat records
1016
+ * new UpdateBuilder(executor, 'habitats', { id: 'PADDOCK-B' })
1017
+ * .add('occupants', 1)
1018
+ * .set('lastTransfer', new Date().toISOString())
1019
+ * .withTransaction(transaction);
1020
+ *
1021
+ * // Execute transfer atomically
1022
+ * await transaction.execute();
1023
+ * ```
1024
+ *
1025
+ * @param transaction - The transaction builder to add this operation to
1026
+ * @returns The builder instance for method chaining
1027
+ */
1028
+ withTransaction(transaction) {
1029
+ const command = this.toDynamoCommand();
1030
+ transaction.putWithCommand(command);
1031
+ return this;
1032
+ }
1033
+ /**
1034
+ * Executes the put operation against DynamoDB.
1035
+ *
1036
+ * @example
1037
+ * ```ts
1038
+ * try {
1039
+ * // Put with condition and return old values
1040
+ * const result = await new PutBuilder(executor, newItem, 'myTable')
1041
+ * .condition(op => op.eq('version', 1))
1042
+ * .returnValues('ALL_OLD')
1043
+ * .execute();
1044
+ *
1045
+ * console.log('Put successful, old item:', result);
1046
+ * } catch (error) {
1047
+ * // Handle condition check failure or other errors
1048
+ * console.error('Put failed:', error);
1049
+ * }
1050
+ * ```
1051
+ *
1052
+ * @returns A promise that resolves to the operation result (type depends on returnValues setting)
1053
+ * @throws Will throw an error if the condition check fails or other DynamoDB errors occur
1054
+ */
1055
+ async execute() {
1056
+ const params = this.toDynamoCommand();
1057
+ return this.executor(params);
1058
+ }
1059
+ /**
1060
+ * Gets a human-readable representation of the put command
1061
+ * with all expression placeholders replaced by their actual values.
1062
+ *
1063
+ * @example
1064
+ * ```ts
1065
+ * const debugInfo = new PutBuilder(executor, {
1066
+ * id: 'RAPTOR-003',
1067
+ * species: 'Velociraptor',
1068
+ * status: 'QUARANTINE',
1069
+ * stats: {
1070
+ * health: 100,
1071
+ * aggressionLevel: 7,
1072
+ * age: 2
1073
+ * }
1074
+ * }, 'dinosaurs')
1075
+ * .condition(op =>
1076
+ * op.and([
1077
+ * op.attributeNotExists('id'),
1078
+ * op.eq('quarantineStatus', 'READY'),
1079
+ * op.gt('securityLevel', 8)
1080
+ * ])
1081
+ * )
1082
+ * .debug();
1083
+ *
1084
+ * console.log('Dinosaur transfer command:', debugInfo);
1085
+ * ```
1086
+ *
1087
+ * @returns A readable representation of the put command with resolved expressions
1088
+ */
1089
+ debug() {
1090
+ const command = this.toDynamoCommand();
1091
+ return debugCommand(command);
1092
+ }
1093
+ };
1094
+
1095
+ // src/builders/delete-builder.ts
1096
+ var DeleteBuilder = class {
1097
+ options = {
1098
+ returnValues: "ALL_OLD"
1099
+ };
1100
+ executor;
1101
+ tableName;
1102
+ key;
1103
+ constructor(executor, tableName, key) {
1104
+ this.executor = executor;
1105
+ this.tableName = tableName;
1106
+ this.key = key;
1107
+ }
1108
+ /**
1109
+ * Adds a condition that must be satisfied for the delete operation to succeed.
1110
+ * Use this method when you need to:
1111
+ * - Ensure safe removal conditions
1112
+ * - Verify habitat status before deletion
1113
+ * - Implement safety protocols
1114
+ *
1115
+ * @example
1116
+ * ```typescript
1117
+ * // Ensure dinosaur can be safely removed
1118
+ * builder.condition(op =>
1119
+ * op.and([
1120
+ * op.eq('status', 'SEDATED'),
1121
+ * op.eq('location', 'MEDICAL_BAY'),
1122
+ * op.attributeExists('lastCheckup')
1123
+ * ])
1124
+ * );
1125
+ *
1126
+ * // Verify habitat is empty
1127
+ * builder.condition(op =>
1128
+ * op.and([
1129
+ * op.eq('occupants', 0),
1130
+ * op.eq('maintenanceStatus', 'COMPLETE'),
1131
+ * op.not(op.attributeExists('activeAlerts'))
1132
+ * ])
1133
+ * );
1134
+ * ```
1135
+ *
1136
+ * @param condition - Either a Condition object or a callback function that builds the condition
1137
+ * @returns The builder instance for method chaining
1138
+ */
1139
+ condition(condition) {
1140
+ if (typeof condition === "function") {
1141
+ const conditionOperator = {
1142
+ eq,
1143
+ ne,
1144
+ lt,
1145
+ lte,
1146
+ gt,
1147
+ gte,
1148
+ between,
1149
+ beginsWith,
1150
+ contains,
1151
+ attributeExists,
1152
+ attributeNotExists,
1153
+ and,
1154
+ or,
1155
+ not
1156
+ };
1157
+ this.options.condition = condition(conditionOperator);
1158
+ } else {
1159
+ this.options.condition = condition;
1160
+ }
1161
+ return this;
1162
+ }
1163
+ /**
1164
+ * Sets whether to return the item's attribute values before deletion.
1165
+ * Use this method when you need to:
1166
+ * - Archive removed dinosaur data
1167
+ * - Track habitat decommissioning history
1168
+ * - Maintain removal audit logs
1169
+ *
1170
+ * @example
1171
+ * ```ts
1172
+ * // Archive dinosaur data before removal
1173
+ * const result = await builder
1174
+ * .returnValues('ALL_OLD')
1175
+ * .execute();
1176
+ *
1177
+ * if (result.item) {
1178
+ * console.log('Removed dinosaur data:', {
1179
+ * species: result.item.species,
1180
+ * age: result.item.age,
1181
+ * lastLocation: result.item.location
1182
+ * });
1183
+ * }
1184
+ * ```
1185
+ *
1186
+ * @param returnValues - Use 'ALL_OLD' to return all attributes of the deleted item
1187
+ * @returns The builder instance for method chaining
1188
+ */
1189
+ returnValues(returnValues) {
1190
+ this.options.returnValues = returnValues;
1191
+ return this;
1192
+ }
1193
+ /**
1194
+ * Generate the DynamoDB command parameters
1195
+ */
1196
+ toDynamoCommand() {
1197
+ const { expression, names, values } = prepareExpressionParams(this.options.condition);
1198
+ return {
1199
+ tableName: this.tableName,
1200
+ key: this.key,
1201
+ conditionExpression: expression,
1202
+ expressionAttributeNames: names,
1203
+ expressionAttributeValues: values,
1204
+ returnValues: this.options.returnValues
1205
+ };
1206
+ }
1207
+ /**
1208
+ * Adds this delete operation to a transaction.
1209
+ * Use this method when you need to:
1210
+ * - Coordinate dinosaur transfers
1211
+ * - Manage habitat decommissioning
1212
+ * - Handle species relocations
1213
+ *
1214
+ * @example
1215
+ * ```ts
1216
+ * const transaction = new TransactionBuilder();
1217
+ *
1218
+ * // Remove dinosaur from old habitat
1219
+ * new DeleteBuilder(executor, 'dinosaurs', { id: 'RAPTOR-001' })
1220
+ * .condition(op => op.eq('status', 'SEDATED'))
1221
+ * .withTransaction(transaction);
1222
+ *
1223
+ * // Update old habitat occupancy
1224
+ * new UpdateBuilder(executor, 'habitats', { id: 'PADDOCK-A' })
1225
+ * .add('occupants', -1)
1226
+ * .withTransaction(transaction);
1227
+ *
1228
+ * // Execute transfer atomically
1229
+ * await transaction.execute();
1230
+ * ```
1231
+ *
1232
+ * @param transaction - The transaction builder to add this operation to
1233
+ */
1234
+ withTransaction(transaction) {
1235
+ const command = this.toDynamoCommand();
1236
+ transaction.deleteWithCommand(command);
1237
+ }
1238
+ /**
1239
+ * Executes the delete operation against DynamoDB.
1240
+ *
1241
+ * @example
1242
+ * ```ts
1243
+ * // Delete with condition and retrieve old values
1244
+ * const result = await new DeleteBuilder(executor, 'myTable', { id: '123' })
1245
+ * .condition(op => op.eq('status', 'INACTIVE'))
1246
+ * .returnValues('ALL_OLD')
1247
+ * .execute();
1248
+ *
1249
+ * if (result.item) {
1250
+ * console.log('Deleted item:', result.item);
1251
+ * }
1252
+ * ```
1253
+ *
1254
+ * @returns A promise that resolves to an object containing the deleted item's attributes (if returnValues is 'ALL_OLD')
1255
+ */
1256
+ async execute() {
1257
+ const params = this.toDynamoCommand();
1258
+ return this.executor(params);
1259
+ }
1260
+ /**
1261
+ * Gets a human-readable representation of the delete command
1262
+ * with all expression placeholders replaced by their actual values.
1263
+ * Use this method when you need to:
1264
+ * - Debug complex deletion conditions
1265
+ * - Verify safety checks
1266
+ * - Log removal operations
1267
+ * - Troubleshoot failed deletions
1268
+ *
1269
+ * @example
1270
+ * ```ts
1271
+ * const debugInfo = new DeleteBuilder(executor, 'dinosaurs', { id: 'TREX-001' })
1272
+ * .condition(op => op.and([
1273
+ * op.eq('status', 'SEDATED'),
1274
+ * op.eq('location', 'MEDICAL_BAY'),
1275
+ * op.gt('sedationLevel', 8)
1276
+ * op.eq('version', 1),
1277
+ * op.attributeExists('status')
1278
+ * ]))
1279
+ * .debug();
1280
+ *
1281
+ * console.log('Delete command:', debugInfo);
1282
+ * ```
1283
+ *
1284
+ * @returns A readable representation of the delete command with resolved expressions
1285
+ */
1286
+ debug() {
1287
+ const command = this.toDynamoCommand();
1288
+ return debugCommand(command);
1289
+ }
1290
+ };
1291
+
1292
+ // src/builders/update-builder.ts
1293
+ var UpdateBuilder = class {
1294
+ updates = [];
1295
+ options = {
1296
+ returnValues: "ALL_NEW"
1297
+ };
1298
+ executor;
1299
+ tableName;
1300
+ key;
1301
+ constructor(executor, tableName, key) {
1302
+ this.executor = executor;
1303
+ this.tableName = tableName;
1304
+ this.key = key;
1305
+ }
1306
+ set(valuesOrPath, value) {
1307
+ if (typeof valuesOrPath === "object") {
1308
+ for (const [key, value2] of Object.entries(valuesOrPath)) {
1309
+ this.updates.push({
1310
+ type: "SET",
1311
+ path: key,
1312
+ value: value2
1313
+ });
1314
+ }
1315
+ } else {
1316
+ this.updates.push({
1317
+ type: "SET",
1318
+ path: valuesOrPath,
1319
+ value
1320
+ });
1321
+ }
1322
+ return this;
1323
+ }
1324
+ /**
1325
+ * Removes an attribute from the item.
1326
+ * Use this method when you need to:
1327
+ * - Delete attributes completely
1328
+ * - Remove nested attributes
1329
+ * - Clean up deprecated fields
1330
+ *
1331
+ * @example
1332
+ * ```typescript
1333
+ * // Remove simple attributes
1334
+ * builder
1335
+ * .remove('temporaryTag')
1336
+ * .remove('previousLocation');
1337
+ *
1338
+ * // Remove nested attributes
1339
+ * builder
1340
+ * .remove('metadata.testData')
1341
+ * .remove('stats.experimentalMetrics');
1342
+ * ```
1343
+ *
1344
+ * @param path - The path to the attribute to remove
1345
+ * @returns The builder instance for method chaining
1346
+ */
1347
+ remove(path) {
1348
+ this.updates.push({
1349
+ type: "REMOVE",
1350
+ path
1351
+ });
1352
+ return this;
1353
+ }
1354
+ /**
1355
+ * Adds a value to a number attribute or adds elements to a set.
1356
+ * Use this method when you need to:
1357
+ * - Increment counters
1358
+ * - Add elements to a set atomically
1359
+ * - Update numerical statistics
1360
+ *
1361
+ * @example
1362
+ * ```typescript
1363
+ * // Increment counters
1364
+ * builder
1365
+ * .add('escapeAttempts', 1)
1366
+ * .add('feedingCount', 1);
1367
+ *
1368
+ * // Add to sets
1369
+ * builder
1370
+ * .add('knownBehaviors', new Set(['PACK_HUNTING', 'AMBUSH_TACTICS']))
1371
+ * .add('visitedZones', new Set(['ZONE_A', 'ZONE_B']));
1372
+ * ```
1373
+ *
1374
+ * @param path - The path to the attribute to update
1375
+ * @param value - The value to add (number or set)
1376
+ * @returns The builder instance for method chaining
1377
+ */
1378
+ add(path, value) {
1379
+ this.updates.push({
1380
+ type: "ADD",
1381
+ path,
1382
+ value
1383
+ });
1384
+ return this;
1385
+ }
1386
+ /**
1387
+ * Removes elements from a set attribute.
1388
+ * Use this method when you need to:
1389
+ * - Remove specific elements from a set
1390
+ * - Update set-based attributes atomically
1391
+ * - Maintain set membership
1392
+ *
1393
+ * @example
1394
+ * ```typescript
1395
+ * // Remove from sets using arrays
1396
+ * builder.deleteElementsFromSet(
1397
+ * 'allowedHabitats',
1398
+ * ['JUNGLE', 'COASTAL']
1399
+ * );
1400
+ *
1401
+ * // Remove from sets using Set objects
1402
+ * builder.deleteElementsFromSet(
1403
+ * 'knownBehaviors',
1404
+ * new Set(['NOCTURNAL', 'TERRITORIAL'])
1405
+ * );
1406
+ *
1407
+ * // Remove from nested sets
1408
+ * builder.deleteElementsFromSet(
1409
+ * 'stats.compatibleSpecies',
1410
+ * ['VELOCIRAPTOR', 'DILOPHOSAURUS']
1411
+ * );
1412
+ * ```
1413
+ *
1414
+ * @param path - The path to the set attribute
1415
+ * @param value - Elements to remove (array or Set)
1416
+ * @returns The builder instance for method chaining
1417
+ */
1418
+ deleteElementsFromSet(path, value) {
1419
+ let valuesToDelete;
1420
+ if (Array.isArray(value)) {
1421
+ valuesToDelete = new Set(value);
1422
+ } else {
1423
+ valuesToDelete = value;
1424
+ }
1425
+ this.updates.push({
1426
+ type: "DELETE",
1427
+ path,
1428
+ value: valuesToDelete
1429
+ });
1430
+ return this;
1431
+ }
1432
+ /**
1433
+ * Adds a condition that must be satisfied for the update to succeed.
1434
+ * Use this method when you need to:
1435
+ * - Implement optimistic locking
1436
+ * - Ensure item state before update
1437
+ * - Validate business rules
1438
+ * - Prevent concurrent modifications
1439
+ *
1440
+ * @example
1441
+ * ```typescript
1442
+ * // Simple condition
1443
+ * builder.condition(op =>
1444
+ * op.eq('status', 'ACTIVE')
1445
+ * );
1446
+ *
1447
+ * // Health check condition
1448
+ * builder.condition(op =>
1449
+ * op.and([
1450
+ * op.gt('health', 50),
1451
+ * op.eq('status', 'HUNTING')
1452
+ * ])
1453
+ * );
1454
+ *
1455
+ * // Complex security condition
1456
+ * builder.condition(op =>
1457
+ * op.and([
1458
+ * op.attributeExists('securitySystem'),
1459
+ * op.eq('containmentStatus', 'SECURE'),
1460
+ * op.lt('aggressionLevel', 8)
1461
+ * ])
1462
+ * );
1463
+ *
1464
+ * // Version check (optimistic locking)
1465
+ * builder.condition(op =>
1466
+ * op.eq('version', currentVersion)
1467
+ * );
1468
+ * ```
1469
+ *
1470
+ * @param condition - Either a Condition object or a callback function that builds the condition
1471
+ * @returns The builder instance for method chaining
1472
+ */
1473
+ condition(condition) {
1474
+ if (typeof condition === "function") {
1475
+ const conditionOperator = {
1476
+ eq,
1477
+ ne,
1478
+ lt,
1479
+ lte,
1480
+ gt,
1481
+ gte,
1482
+ between,
1483
+ beginsWith,
1484
+ contains,
1485
+ attributeExists,
1486
+ attributeNotExists,
1487
+ and,
1488
+ or,
1489
+ not
1490
+ };
1491
+ this.options.condition = condition(conditionOperator);
1492
+ } else {
1493
+ this.options.condition = condition;
1494
+ }
1495
+ return this;
1496
+ }
1497
+ /**
1498
+ * Sets which item attributes to include in the response.
1499
+ * Use this method when you need to:
1500
+ * - Get the complete updated item
1501
+ * - Track changes to specific attributes
1502
+ * - Compare old and new values
1503
+ * - Monitor attribute modifications
1504
+ *
1505
+ * Available options:
1506
+ * - ALL_NEW: All attributes after the update
1507
+ * - UPDATED_NEW: Only updated attributes, new values
1508
+ * - ALL_OLD: All attributes before the update
1509
+ * - UPDATED_OLD: Only updated attributes, old values
1510
+ * - NONE: No attributes returned (default)
1511
+ *
1512
+ * @example
1513
+ * ```typescript
1514
+ * // Get complete updated dinosaur
1515
+ * const result = await builder
1516
+ * .set('status', 'SLEEPING')
1517
+ * .returnValues('ALL_NEW')
1518
+ * .execute();
1519
+ *
1520
+ * // Track specific attribute changes
1521
+ * const result = await builder
1522
+ * .set({
1523
+ * 'stats.health': 100,
1524
+ * 'stats.energy': 95
1525
+ * })
1526
+ * .returnValues('UPDATED_OLD')
1527
+ * .execute();
1528
+ *
1529
+ * if (result.item) {
1530
+ * console.log('Previous health:', result.item.stats?.health);
1531
+ * }
1532
+ * ```
1533
+ *
1534
+ * @param returnValues - Which attributes to return in the response
1535
+ * @returns The builder instance for method chaining
1536
+ */
1537
+ returnValues(returnValues) {
1538
+ this.options.returnValues = returnValues;
1539
+ return this;
1540
+ }
1541
+ /**
1542
+ * Generate the DynamoDB command parameters
1543
+ */
1544
+ toDynamoCommand() {
1545
+ if (this.updates.length === 0) {
1546
+ throw new Error("No update actions specified");
1547
+ }
1548
+ const expressionParams = {
1549
+ expressionAttributeNames: {},
1550
+ expressionAttributeValues: {},
1551
+ valueCounter: { count: 0 }
1552
+ };
1553
+ let updateExpression = "";
1554
+ const setUpdates = [];
1555
+ const removeUpdates = [];
1556
+ const addUpdates = [];
1557
+ const deleteUpdates = [];
1558
+ for (const update of this.updates) {
1559
+ switch (update.type) {
1560
+ case "SET":
1561
+ setUpdates.push(update);
1562
+ break;
1563
+ case "REMOVE":
1564
+ removeUpdates.push(update);
1565
+ break;
1566
+ case "ADD":
1567
+ addUpdates.push(update);
1568
+ break;
1569
+ case "DELETE":
1570
+ deleteUpdates.push(update);
1571
+ break;
1572
+ }
1573
+ }
1574
+ if (setUpdates.length > 0) {
1575
+ updateExpression += "SET ";
1576
+ updateExpression += setUpdates.map((update) => {
1577
+ const attrName = generateAttributeName(expressionParams, update.path);
1578
+ const valueName = generateValueName(expressionParams, update.value);
1579
+ expressionParams.expressionAttributeValues[valueName] = update.value;
1580
+ return `${attrName} = ${valueName}`;
1581
+ }).join(", ");
1582
+ }
1583
+ if (removeUpdates.length > 0) {
1584
+ if (updateExpression) {
1585
+ updateExpression += " ";
1586
+ }
1587
+ updateExpression += "REMOVE ";
1588
+ updateExpression += removeUpdates.map((update) => {
1589
+ return generateAttributeName(expressionParams, update.path);
1590
+ }).join(", ");
1591
+ }
1592
+ if (addUpdates.length > 0) {
1593
+ if (updateExpression) {
1594
+ updateExpression += " ";
1595
+ }
1596
+ updateExpression += "ADD ";
1597
+ updateExpression += addUpdates.map((update) => {
1598
+ const attrName = generateAttributeName(expressionParams, update.path);
1599
+ const valueName = generateValueName(expressionParams, update.value);
1600
+ return `${attrName} ${valueName}`;
1601
+ }).join(", ");
1602
+ }
1603
+ if (deleteUpdates.length > 0) {
1604
+ if (updateExpression) {
1605
+ updateExpression += " ";
1606
+ }
1607
+ updateExpression += "DELETE ";
1608
+ updateExpression += deleteUpdates.map((update) => {
1609
+ const attrName = generateAttributeName(expressionParams, update.path);
1610
+ const valueName = generateValueName(expressionParams, update.value);
1611
+ return `${attrName} ${valueName}`;
1612
+ }).join(", ");
1613
+ }
1614
+ let conditionExpression;
1615
+ if (this.options.condition) {
1616
+ conditionExpression = buildExpression(this.options.condition, expressionParams);
1617
+ }
1618
+ const { expressionAttributeNames, expressionAttributeValues } = expressionParams;
1619
+ return {
1620
+ tableName: this.tableName,
1621
+ key: this.key,
1622
+ updateExpression,
1623
+ conditionExpression,
1624
+ expressionAttributeNames: Object.keys(expressionAttributeNames).length > 0 ? expressionAttributeNames : void 0,
1625
+ expressionAttributeValues: Object.keys(expressionAttributeValues).length > 0 ? expressionAttributeValues : void 0,
1626
+ returnValues: this.options.returnValues
1627
+ };
1628
+ }
1629
+ /**
1630
+ * Adds this update operation to a transaction.
1631
+ * Use this method when you need to:
1632
+ * - Update items as part of a larger transaction
1633
+ * - Ensure multiple updates are atomic
1634
+ * - Coordinate updates across multiple items
1635
+ *
1636
+ * @example
1637
+ * ```typescript
1638
+ * const transaction = new TransactionBuilder(executor);
1639
+ *
1640
+ * // Update dinosaur status and habitat occupancy atomically
1641
+ * new UpdateBuilder(executor, 'dinosaurs', { id: 'TREX-001' })
1642
+ * .set('location', 'PADDOCK_A')
1643
+ * .set('status', 'CONTAINED')
1644
+ * .withTransaction(transaction);
1645
+ *
1646
+ * new UpdateBuilder(executor, 'habitats', { id: 'PADDOCK-A' })
1647
+ * .add('occupants', 1)
1648
+ * .set('lastOccupied', new Date().toISOString())
1649
+ * .withTransaction(transaction);
1650
+ *
1651
+ * // Execute all operations atomically
1652
+ * await transaction.execute();
1653
+ * ```
1654
+ *
1655
+ * @param transaction - The transaction builder to add this operation to
1656
+ * @returns The builder instance for method chaining
1657
+ */
1658
+ withTransaction(transaction) {
1659
+ const command = this.toDynamoCommand();
1660
+ transaction.updateWithCommand(command);
1661
+ }
1662
+ /**
1663
+ * Gets a human-readable representation of the update command.
1664
+ * Use this method when you need to:
1665
+ * - Debug complex update expressions
1666
+ * - Verify attribute names and values
1667
+ * - Log update operations
1668
+ * - Troubleshoot condition expressions
1669
+ *
1670
+ * @example
1671
+ * ```typescript
1672
+ * // Create complex update
1673
+ * const builder = new UpdateBuilder(executor, 'dinosaurs', { id: 'RAPTOR-001' })
1674
+ * .set({
1675
+ * status: 'HUNTING',
1676
+ * 'stats.health': 95,
1677
+ * 'behavior.lastObserved': new Date().toISOString()
1678
+ * })
1679
+ * .add('huntingSuccesses', 1)
1680
+ * .condition(op => op.gt('health', 50));
1681
+ *
1682
+ * // Debug the update
1683
+ * const debugInfo = builder.debug();
1684
+ * console.log('Update operation:', debugInfo);
1685
+ * ```
1686
+ *
1687
+ * @returns A readable representation of the update command with resolved expressions
1688
+ */
1689
+ debug() {
1690
+ const command = this.toDynamoCommand();
1691
+ return debugCommand(command);
1692
+ }
1693
+ /**
1694
+ * Executes the update operation against DynamoDB.
1695
+ * Use this method when you need to:
1696
+ * - Apply updates immediately
1697
+ * - Get the updated item values
1698
+ * - Handle conditional update failures
1699
+ *
1700
+ * @example
1701
+ * ```typescript
1702
+ * try {
1703
+ * // Update dinosaur status with conditions
1704
+ * const result = await new UpdateBuilder(executor, 'dinosaurs', { id: 'TREX-001' })
1705
+ * .set({
1706
+ * status: 'FEEDING',
1707
+ * lastMeal: new Date().toISOString(),
1708
+ * 'stats.hunger': 0
1709
+ * })
1710
+ * .add('feedingCount', 1)
1711
+ * .condition(op =>
1712
+ * op.and([
1713
+ * op.gt('stats.hunger', 80),
1714
+ * op.eq('status', 'HUNTING')
1715
+ * ])
1716
+ * )
1717
+ * .returnValues('ALL_NEW')
1718
+ * .execute();
1719
+ *
1720
+ * if (result.item) {
1721
+ * console.log('Updated dinosaur:', result.item);
1722
+ * }
1723
+ * } catch (error) {
1724
+ * // Handle condition check failure
1725
+ * console.error('Failed to update dinosaur:', error);
1726
+ * // Check if dinosaur wasn't hungry enough
1727
+ * if (error.name === 'ConditionalCheckFailedException') {
1728
+ * console.log('Dinosaur not ready for feeding');
1729
+ * }
1730
+ * }
1731
+ * ```
1732
+ *
1733
+ * @returns A promise that resolves to an object containing the updated item (if returnValues is set)
1734
+ * @throws {ConditionalCheckFailedException} If the condition check fails
1735
+ * @throws {Error} If the update operation fails for other reasons
1736
+ */
1737
+ async execute() {
1738
+ const params = this.toDynamoCommand();
1739
+ return this.executor(params);
1740
+ }
1741
+ };
1742
+
1743
+ // src/utils/debug-transaction.ts
1744
+ function debugTransactionItem(item) {
1745
+ const result = {
1746
+ type: item.type,
1747
+ tableName: item.params.tableName
1748
+ };
1749
+ if ("key" in item.params) {
1750
+ result.key = item.params.key;
1751
+ }
1752
+ if (item.type === "Put") {
1753
+ result.item = item.params.item;
1754
+ }
1755
+ switch (item.type) {
1756
+ case "Put":
1757
+ case "Delete":
1758
+ case "ConditionCheck":
1759
+ result.readable = debugCommand(item.params).readable;
1760
+ break;
1761
+ case "Update":
1762
+ result.readable = debugCommand(item.params).readable;
1763
+ break;
1764
+ }
1765
+ return result;
1766
+ }
1767
+ function debugTransaction(items) {
1768
+ return items.map((item) => debugTransactionItem(item));
1769
+ }
1770
+
1771
+ // src/builders/transaction-builder.ts
1772
+ var TransactionBuilder = class {
1773
+ items = [];
1774
+ options = {};
1775
+ indexConfig;
1776
+ executor;
1777
+ constructor(executor, indexConfig) {
1778
+ this.executor = executor;
1779
+ this.indexConfig = indexConfig;
1780
+ }
1781
+ /**
1782
+ * Checks if an item with the same primary key already exists in the transaction
1783
+ * @private
1784
+ */
1785
+ checkForDuplicateItem(tableName, newItem) {
1786
+ const pkName = this.indexConfig.partitionKey;
1787
+ const skName = this.indexConfig.sortKey || "";
1788
+ const pkValue = newItem[pkName];
1789
+ const skValue = skName ? newItem[skName] : void 0;
1790
+ if (!pkValue) {
1791
+ throw new Error(`Primary key value for '${pkName}' is missing`);
1792
+ }
1793
+ const duplicateItem = this.items.find((item) => {
1794
+ let itemKey;
1795
+ let itemTableName;
1796
+ switch (item.type) {
1797
+ case "Put":
1798
+ itemTableName = item.params.tableName;
1799
+ itemKey = item.params.item;
1800
+ break;
1801
+ case "Update":
1802
+ case "Delete":
1803
+ case "ConditionCheck":
1804
+ itemTableName = item.params.tableName;
1805
+ itemKey = item.params.key;
1806
+ break;
1807
+ }
1808
+ if (itemTableName === tableName && itemKey) {
1809
+ const itemPkValue = itemKey[pkName];
1810
+ const itemSkValue = skName ? itemKey[skName] : void 0;
1811
+ if (itemPkValue === pkValue) {
1812
+ if (skValue === void 0 && itemSkValue === void 0) {
1813
+ return true;
1814
+ }
1815
+ if (skValue !== void 0 && itemSkValue !== void 0 && skValue === itemSkValue) {
1816
+ return true;
1817
+ }
1818
+ }
1819
+ }
1820
+ return false;
1821
+ });
1822
+ if (duplicateItem) {
1823
+ throw new Error(
1824
+ `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.`
1825
+ );
1826
+ }
1827
+ }
1828
+ /**
1829
+ * Adds a put operation to the transaction.
1830
+ * Use this method when you need to:
1831
+ * - Insert new items as part of a transaction
1832
+ * - Replace existing items atomically
1833
+ * - Ensure items meet certain conditions before insertion
1834
+ *
1835
+ * The method automatically checks for duplicate items within the transaction
1836
+ * to prevent multiple operations on the same item.
1837
+ *
1838
+ * @example
1839
+ * ```typescript
1840
+ * // Simple put operation
1841
+ * transaction.put('orders', {
1842
+ * orderId: '123',
1843
+ * status: 'PENDING',
1844
+ * amount: 100
1845
+ * });
1846
+ *
1847
+ * // Conditional put operation
1848
+ * transaction.put(
1849
+ * 'inventory',
1850
+ * { productId: 'ABC', quantity: 50 },
1851
+ * op => op.attributeNotExists('productId')
1852
+ * );
1853
+ *
1854
+ * // Put with complex condition
1855
+ * transaction.put(
1856
+ * 'users',
1857
+ * { userId: '123', status: 'ACTIVE' },
1858
+ * op => op.and([
1859
+ * op.attributeNotExists('userId'),
1860
+ * op.beginsWith('status', 'ACTIVE')
1861
+ * ])
1862
+ * );
1863
+ * ```
1864
+ *
1865
+ * @param tableName - The name of the DynamoDB table
1866
+ * @param item - The item to put into the table
1867
+ * @param condition - Optional condition that must be satisfied
1868
+ * @returns The transaction builder for method chaining
1869
+ * @throws {Error} If a duplicate item is detected in the transaction
1870
+ */
1871
+ put(tableName, item, condition) {
1872
+ this.checkForDuplicateItem(tableName, item);
1873
+ const transactionItem = {
1874
+ type: "Put",
1875
+ params: {
1876
+ tableName,
1877
+ item
1878
+ }
1879
+ };
1880
+ if (condition) {
1881
+ const { expression, names, values } = prepareExpressionParams(condition);
1882
+ transactionItem.params.conditionExpression = expression;
1883
+ transactionItem.params.expressionAttributeNames = names;
1884
+ transactionItem.params.expressionAttributeValues = values;
1885
+ }
1886
+ this.items.push(transactionItem);
1887
+ return this;
1888
+ }
1889
+ /**
1890
+ * Adds a pre-configured put operation to the transaction.
1891
+ * Use this method when you need to:
1892
+ * - Reuse put commands from PutBuilder
1893
+ * - Add complex put operations with pre-configured parameters
1894
+ * - Integrate with existing put command configurations
1895
+ *
1896
+ * This method is particularly useful when working with PutBuilder
1897
+ * to maintain consistency in put operations across your application.
1898
+ *
1899
+ * @example
1900
+ * ```typescript
1901
+ * // Create a put command with PutBuilder
1902
+ * const putCommand = new PutBuilder(executor, newItem, 'users')
1903
+ * .condition(op => op.attributeNotExists('userId'))
1904
+ * .toDynamoCommand();
1905
+ *
1906
+ * // Add the command to the transaction
1907
+ * transaction.putWithCommand(putCommand);
1908
+ * ```
1909
+ *
1910
+ * @param command - The complete put command configuration
1911
+ * @returns The transaction builder for method chaining
1912
+ * @throws {Error} If a duplicate item is detected in the transaction
1913
+ * @see PutBuilder for creating put commands
1914
+ */
1915
+ putWithCommand(command) {
1916
+ this.checkForDuplicateItem(command.tableName, command.item);
1917
+ const transactionItem = {
1918
+ type: "Put",
1919
+ params: command
1920
+ };
1921
+ this.items.push(transactionItem);
1922
+ return this;
1923
+ }
1924
+ /**
1925
+ * Adds a delete operation to the transaction.
1926
+ * Use this method when you need to:
1927
+ * - Remove items as part of a transaction
1928
+ * - Conditionally delete items
1929
+ * - Ensure items exist before deletion
1930
+ *
1931
+ * The method automatically checks for duplicate items within the transaction
1932
+ * to prevent multiple operations on the same item.
1933
+ *
1934
+ * @example
1935
+ * ```typescript
1936
+ * // Simple delete operation
1937
+ * transaction.delete('orders', {
1938
+ * pk: 'ORDER#123',
1939
+ * sk: 'METADATA'
1940
+ * });
1941
+ *
1942
+ * // Conditional delete operation
1943
+ * transaction.delete(
1944
+ * 'users',
1945
+ * { pk: 'USER#123' },
1946
+ * op => op.eq('status', 'INACTIVE')
1947
+ * );
1948
+ *
1949
+ * // Delete with complex condition
1950
+ * transaction.delete(
1951
+ * 'products',
1952
+ * { pk: 'PROD#ABC' },
1953
+ * op => op.and([
1954
+ * op.eq('status', 'DRAFT'),
1955
+ * op.lt('version', 5)
1956
+ * ])
1957
+ * );
1958
+ * ```
1959
+ *
1960
+ * @param tableName - The name of the DynamoDB table
1961
+ * @param key - The primary key of the item to delete
1962
+ * @param condition - Optional condition that must be satisfied
1963
+ * @returns The transaction builder for method chaining
1964
+ * @throws {Error} If a duplicate item is detected in the transaction
1965
+ */
1966
+ delete(tableName, key, condition) {
1967
+ this.checkForDuplicateItem(tableName, key);
1968
+ const transactionItem = {
1969
+ type: "Delete",
1970
+ params: {
1971
+ tableName,
1972
+ key: {
1973
+ pk: key.pk,
1974
+ sk: key.sk
1975
+ }
1976
+ }
1977
+ };
1978
+ if (condition) {
1979
+ const { expression, names, values } = prepareExpressionParams(condition);
1980
+ transactionItem.params.conditionExpression = expression;
1981
+ transactionItem.params.expressionAttributeNames = names;
1982
+ transactionItem.params.expressionAttributeValues = values;
1983
+ }
1984
+ this.items.push(transactionItem);
1985
+ return this;
1986
+ }
1987
+ /**
1988
+ * Adds a pre-configured delete operation to the transaction.
1989
+ * Use this method when you need to:
1990
+ * - Reuse delete commands from DeleteBuilder
1991
+ * - Add complex delete operations with pre-configured parameters
1992
+ * - Integrate with existing delete command configurations
1993
+ *
1994
+ * This method is particularly useful when working with DeleteBuilder
1995
+ * to maintain consistency in delete operations across your application.
1996
+ *
1997
+ * @example
1998
+ * ```typescript
1999
+ * // Create a delete command with DeleteBuilder
2000
+ * const deleteCommand = new DeleteBuilder(executor, 'users', { pk: 'USER#123' })
2001
+ * .condition(op => op.and([
2002
+ * op.attributeExists('pk'),
2003
+ * op.eq('status', 'INACTIVE')
2004
+ * ]))
2005
+ * .toDynamoCommand();
2006
+ *
2007
+ * // Add the command to the transaction
2008
+ * transaction.deleteWithCommand(deleteCommand);
2009
+ * ```
2010
+ *
2011
+ * @param command - The complete delete command configuration
2012
+ * @returns The transaction builder for method chaining
2013
+ * @throws {Error} If a duplicate item is detected in the transaction
2014
+ * @see DeleteBuilder for creating delete commands
2015
+ */
2016
+ deleteWithCommand(command) {
2017
+ this.checkForDuplicateItem(command.tableName, command.key);
2018
+ const transactionItem = {
2019
+ type: "Delete",
2020
+ params: command
2021
+ };
2022
+ this.items.push(transactionItem);
2023
+ return this;
2024
+ }
2025
+ /**
2026
+ * Adds an update operation to the transaction.
2027
+ * Use this method when you need to:
2028
+ * - Modify existing items as part of a transaction
2029
+ * - Update multiple attributes atomically
2030
+ * - Apply conditional updates
2031
+ * - Perform complex attribute manipulations
2032
+ *
2033
+ * The method supports all DynamoDB update expressions:
2034
+ * - SET: Modify or add attributes
2035
+ * - REMOVE: Delete attributes
2036
+ * - ADD: Update numbers and sets
2037
+ * - DELETE: Remove elements from a set
2038
+ *
2039
+ * @example
2040
+ * ```typescript
2041
+ * // Simple update
2042
+ * transaction.update(
2043
+ * 'orders',
2044
+ * { pk: 'ORDER#123' },
2045
+ * 'SET #status = :status',
2046
+ * { '#status': 'status' },
2047
+ * { ':status': 'PROCESSING' }
2048
+ * );
2049
+ *
2050
+ * // Complex update with multiple operations
2051
+ * transaction.update(
2052
+ * 'products',
2053
+ * { pk: 'PROD#ABC' },
2054
+ * 'SET #qty = #qty - :amount, #status = :status REMOVE #oldAttr',
2055
+ * { '#qty': 'quantity', '#status': 'status', '#oldAttr': 'deprecated_field' },
2056
+ * { ':amount': 1, ':status': 'LOW_STOCK' }
2057
+ * );
2058
+ *
2059
+ * // Conditional update
2060
+ * transaction.update(
2061
+ * 'users',
2062
+ * { pk: 'USER#123' },
2063
+ * 'SET #lastLogin = :now',
2064
+ * { '#lastLogin': 'lastLoginDate' },
2065
+ * { ':now': new Date().toISOString() },
2066
+ * op => op.attributeExists('pk')
2067
+ * );
2068
+ * ```
2069
+ *
2070
+ * @param tableName - The name of the DynamoDB table
2071
+ * @param key - The primary key of the item to update
2072
+ * @param updateExpression - The update expression (SET, REMOVE, ADD, DELETE)
2073
+ * @param expressionAttributeNames - Map of attribute name placeholders to actual names
2074
+ * @param expressionAttributeValues - Map of value placeholders to actual values
2075
+ * @param condition - Optional condition that must be satisfied
2076
+ * @returns The transaction builder for method chaining
2077
+ * @throws {Error} If a duplicate item is detected in the transaction
2078
+ */
2079
+ update(tableName, key, updateExpression, expressionAttributeNames, expressionAttributeValues, condition) {
2080
+ this.checkForDuplicateItem(tableName, key);
2081
+ const transactionItem = {
2082
+ type: "Update",
2083
+ params: {
2084
+ tableName,
2085
+ key: {
2086
+ pk: key.pk,
2087
+ sk: key.sk
2088
+ },
2089
+ updateExpression,
2090
+ expressionAttributeNames,
2091
+ expressionAttributeValues
2092
+ }
2093
+ };
2094
+ if (condition) {
2095
+ const { expression, names, values } = prepareExpressionParams(condition);
2096
+ transactionItem.params.conditionExpression = expression;
2097
+ transactionItem.params.expressionAttributeNames = {
2098
+ ...transactionItem.params.expressionAttributeNames,
2099
+ ...names
2100
+ };
2101
+ transactionItem.params.expressionAttributeValues = {
2102
+ ...transactionItem.params.expressionAttributeValues,
2103
+ ...values
2104
+ };
2105
+ }
2106
+ this.items.push(transactionItem);
2107
+ return this;
2108
+ }
2109
+ /**
2110
+ * Adds a pre-configured update operation to the transaction.
2111
+ * Use this method when you need to:
2112
+ * - Reuse update commands from UpdateBuilder
2113
+ * - Add complex update operations with pre-configured parameters
2114
+ * - Integrate with existing update command configurations
2115
+ *
2116
+ * This method is particularly useful when working with UpdateBuilder
2117
+ * to maintain consistency in update operations across your application.
2118
+ *
2119
+ * @example
2120
+ * ```typescript
2121
+ * // Create an update command with UpdateBuilder
2122
+ * const updateCommand = new UpdateBuilder(executor, 'inventory', { pk: 'PROD#ABC' })
2123
+ * .set('quantity', ':qty')
2124
+ * .set('lastUpdated', ':now')
2125
+ * .values({
2126
+ * ':qty': 100,
2127
+ * ':now': new Date().toISOString()
2128
+ * })
2129
+ * .condition(op => op.gt('quantity', 0))
2130
+ * .toDynamoCommand();
2131
+ *
2132
+ * // Add the command to the transaction
2133
+ * transaction.updateWithCommand(updateCommand);
2134
+ * ```
2135
+ *
2136
+ * @param command - The complete update command configuration
2137
+ * @returns The transaction builder for method chaining
2138
+ * @throws {Error} If a duplicate item is detected in the transaction
2139
+ * @see UpdateBuilder for creating update commands
2140
+ */
2141
+ updateWithCommand(command) {
2142
+ this.checkForDuplicateItem(command.tableName, command.key);
2143
+ const transactionItem = {
2144
+ type: "Update",
2145
+ params: command
2146
+ };
2147
+ this.items.push(transactionItem);
2148
+ return this;
2149
+ }
2150
+ /**
2151
+ * Adds a condition check operation to the transaction.
2152
+ * Use this method when you need to:
2153
+ * - Validate item state without modifying it
2154
+ * - Ensure data consistency across tables
2155
+ * - Implement complex business rules
2156
+ * - Verify preconditions for other operations
2157
+ *
2158
+ * Condition checks are particularly useful for:
2159
+ * - Implementing optimistic locking
2160
+ * - Ensuring referential integrity
2161
+ * - Validating business rules atomically
2162
+ *
2163
+ * @example
2164
+ * ```typescript
2165
+ * // Check if order is in correct state
2166
+ * transaction.conditionCheck(
2167
+ * 'orders',
2168
+ * { pk: 'ORDER#123' },
2169
+ * op => op.eq('status', 'PENDING')
2170
+ * );
2171
+ *
2172
+ * // Complex condition check
2173
+ * transaction.conditionCheck(
2174
+ * 'inventory',
2175
+ * { pk: 'PROD#ABC' },
2176
+ * op => op.and([
2177
+ * op.gt('quantity', 0),
2178
+ * op.eq('status', 'ACTIVE'),
2179
+ * op.attributeExists('lastRestockDate')
2180
+ * ])
2181
+ * );
2182
+ *
2183
+ * // Check with multiple attributes
2184
+ * transaction.conditionCheck(
2185
+ * 'users',
2186
+ * { pk: 'USER#123' },
2187
+ * op => op.or([
2188
+ * op.eq('status', 'PREMIUM'),
2189
+ * op.gte('credits', 100)
2190
+ * ])
2191
+ * );
2192
+ * ```
2193
+ *
2194
+ * @param tableName - The name of the DynamoDB table
2195
+ * @param key - The primary key of the item to check
2196
+ * @param condition - The condition that must be satisfied
2197
+ * @returns The transaction builder for method chaining
2198
+ * @throws {Error} If a duplicate item is detected in the transaction
2199
+ * @throws {Error} If condition expression generation fails
2200
+ */
2201
+ conditionCheck(tableName, key, condition) {
2202
+ this.checkForDuplicateItem(tableName, key);
2203
+ const { expression, names, values } = prepareExpressionParams(condition);
2204
+ if (!expression) {
2205
+ throw new Error("Failed to generate condition expression");
2206
+ }
2207
+ const transactionItem = {
2208
+ type: "ConditionCheck",
2209
+ params: {
2210
+ tableName,
2211
+ key: {
2212
+ pk: key.pk,
2213
+ sk: key.sk
2214
+ },
2215
+ conditionExpression: expression,
2216
+ expressionAttributeNames: names,
2217
+ expressionAttributeValues: values
2218
+ }
2219
+ };
2220
+ this.items.push(transactionItem);
2221
+ return this;
2222
+ }
2223
+ /**
2224
+ * Adds a pre-configured condition check operation to the transaction.
2225
+ * Use this method when you need to:
2226
+ * - Reuse condition checks from ConditionCheckBuilder
2227
+ * - Add complex condition checks with pre-configured parameters
2228
+ * - Integrate with existing condition check configurations
2229
+ *
2230
+ * This method is particularly useful when working with ConditionCheckBuilder
2231
+ * to maintain consistency in condition checks across your application.
2232
+ *
2233
+ * @example
2234
+ * ```typescript
2235
+ * // Create a condition check with ConditionCheckBuilder
2236
+ * const checkCommand = new ConditionCheckBuilder('inventory', { pk: 'PROD#ABC' })
2237
+ * .condition(op => op.and([
2238
+ * op.between('quantity', 10, 100),
2239
+ * op.beginsWith('category', 'ELECTRONICS'),
2240
+ * op.attributeExists('lastAuditDate')
2241
+ * ]))
2242
+ * .toDynamoCommand();
2243
+ *
2244
+ * // Add the command to the transaction
2245
+ * transaction.conditionCheckWithCommand(checkCommand);
2246
+ * ```
2247
+ *
2248
+ * @param command - The complete condition check command configuration
2249
+ * @returns The transaction builder for method chaining
2250
+ * @throws {Error} If a duplicate item is detected in the transaction
2251
+ * @see ConditionCheckBuilder for creating condition check commands
2252
+ */
2253
+ conditionCheckWithCommand(command) {
2254
+ this.checkForDuplicateItem(command.tableName, command.key);
2255
+ const transactionItem = {
2256
+ type: "ConditionCheck",
2257
+ params: command
2258
+ };
2259
+ this.items.push(transactionItem);
2260
+ return this;
2261
+ }
2262
+ /**
2263
+ * Sets options for the transaction execution.
2264
+ * Use this method when you need to:
2265
+ * - Enable idempotent transactions
2266
+ * - Track consumed capacity
2267
+ * - Monitor item collection metrics
2268
+ *
2269
+ * @example
2270
+ * ```typescript
2271
+ * // Enable idempotency and capacity tracking
2272
+ * transaction.withOptions({
2273
+ * clientRequestToken: 'unique-request-id-123',
2274
+ * returnConsumedCapacity: 'TOTAL'
2275
+ * });
2276
+ *
2277
+ * // Track item collection metrics
2278
+ * transaction.withOptions({
2279
+ * returnItemCollectionMetrics: 'SIZE'
2280
+ * });
2281
+ * ```
2282
+ *
2283
+ * Note: ClientRequestToken can be used to make transactions idempotent,
2284
+ * ensuring the same transaction is not executed multiple times.
2285
+ *
2286
+ * @param options - Configuration options for the transaction
2287
+ * @returns The transaction builder for method chaining
2288
+ */
2289
+ withOptions(options) {
2290
+ this.options = { ...this.options, ...options };
2291
+ return this;
2292
+ }
2293
+ /**
2294
+ * Gets a human-readable representation of the transaction items.
2295
+ * Use this method when you need to:
2296
+ * - Debug complex transactions
2297
+ * - Verify operation parameters
2298
+ * - Log transaction details
2299
+ * - Troubleshoot condition expressions
2300
+ *
2301
+ * The method resolves all expression placeholders with their actual values,
2302
+ * making it easier to understand the transaction's operations.
2303
+ *
2304
+ * @example
2305
+ * ```typescript
2306
+ * // Add multiple operations
2307
+ * transaction
2308
+ * .put('orders', { orderId: '123', status: 'PENDING' })
2309
+ * .update('inventory',
2310
+ * { productId: 'ABC' },
2311
+ * 'SET quantity = quantity - :amount',
2312
+ * undefined,
2313
+ * { ':amount': 1 }
2314
+ * );
2315
+ *
2316
+ * // Debug the transaction
2317
+ * const debugInfo = transaction.debug();
2318
+ * console.log('Transaction operations:', debugInfo);
2319
+ * ```
2320
+ *
2321
+ * @returns An array of readable representations of the transaction items
2322
+ */
2323
+ debug() {
2324
+ return debugTransaction(this.items);
2325
+ }
2326
+ /**
2327
+ * Executes all operations in the transaction atomically.
2328
+ * Use this method when you need to:
2329
+ * - Perform multiple operations atomically
2330
+ * - Ensure all-or-nothing execution
2331
+ * - Maintain data consistency across operations
2332
+ *
2333
+ * The transaction will only succeed if all operations succeed.
2334
+ * If any operation fails, the entire transaction is rolled back.
2335
+ *
2336
+ * @example
2337
+ * ```typescript
2338
+ * try {
2339
+ * // Build and execute transaction
2340
+ * await transaction
2341
+ * .put('orders', newOrder)
2342
+ * .update('inventory',
2343
+ * { productId: 'ABC' },
2344
+ * 'SET quantity = quantity - :qty',
2345
+ * undefined,
2346
+ * { ':qty': 1 }
2347
+ * )
2348
+ * .conditionCheck('products',
2349
+ * { productId: 'ABC' },
2350
+ * op => op.eq('status', 'ACTIVE')
2351
+ * )
2352
+ * .execute();
2353
+ *
2354
+ * console.log('Transaction completed successfully');
2355
+ * } catch (error) {
2356
+ * // Handle transaction failure
2357
+ * console.error('Transaction failed:', error);
2358
+ * }
2359
+ * ```
2360
+ *
2361
+ * @throws {Error} If no transaction items are specified
2362
+ * @throws {Error} If any operation in the transaction fails
2363
+ * @returns A promise that resolves when the transaction completes
2364
+ */
2365
+ async execute() {
2366
+ if (this.items.length === 0) {
2367
+ throw new Error("No transaction items specified");
2368
+ }
2369
+ const transactItems = this.items.map((item) => {
2370
+ switch (item.type) {
2371
+ case "Put":
2372
+ return {
2373
+ Put: {
2374
+ TableName: item.params.tableName,
2375
+ Item: item.params.item,
2376
+ ConditionExpression: item.params.conditionExpression,
2377
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
2378
+ ExpressionAttributeValues: item.params.expressionAttributeValues
2379
+ }
2380
+ };
2381
+ case "Delete":
2382
+ return {
2383
+ Delete: {
2384
+ TableName: item.params.tableName,
2385
+ Key: item.params.key,
2386
+ ConditionExpression: item.params.conditionExpression,
2387
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
2388
+ ExpressionAttributeValues: item.params.expressionAttributeValues
2389
+ }
2390
+ };
2391
+ case "Update":
2392
+ return {
2393
+ Update: {
2394
+ TableName: item.params.tableName,
2395
+ Key: item.params.key,
2396
+ UpdateExpression: item.params.updateExpression,
2397
+ ConditionExpression: item.params.conditionExpression,
2398
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
2399
+ ExpressionAttributeValues: item.params.expressionAttributeValues
2400
+ }
2401
+ };
2402
+ case "ConditionCheck":
2403
+ return {
2404
+ ConditionCheck: {
2405
+ TableName: item.params.tableName,
2406
+ Key: item.params.key,
2407
+ ConditionExpression: item.params.conditionExpression,
2408
+ ExpressionAttributeNames: item.params.expressionAttributeNames,
2409
+ ExpressionAttributeValues: item.params.expressionAttributeValues
2410
+ }
2411
+ };
2412
+ default: {
2413
+ const exhaustiveCheck = item;
2414
+ throw new Error(`Unsupported transaction item type: ${String(exhaustiveCheck)}`);
2415
+ }
2416
+ }
2417
+ });
2418
+ const params = {
2419
+ TransactItems: transactItems,
2420
+ ClientRequestToken: this.options.clientRequestToken,
2421
+ ReturnConsumedCapacity: this.options.returnConsumedCapacity,
2422
+ ReturnItemCollectionMetrics: this.options.returnItemCollectionMetrics
2423
+ };
2424
+ try {
2425
+ await this.executor(params);
2426
+ } catch (error) {
2427
+ console.log(this.debug());
2428
+ console.error("Error executing transaction:", error);
2429
+ throw error;
2430
+ }
2431
+ }
2432
+ };
2433
+
2434
+ // src/utils/chunk-array.ts
2435
+ function* chunkArray(array, size) {
2436
+ if (size <= 0) {
2437
+ throw new Error("Chunk size must be greater than 0");
2438
+ }
2439
+ for (let i = 0; i < array.length; i += size) {
2440
+ yield array.slice(i, i + size);
2441
+ }
2442
+ }
2443
+
2444
+ // src/builders/condition-check-builder.ts
2445
+ var ConditionCheckBuilder = class {
2446
+ key;
2447
+ tableName;
2448
+ conditionExpression;
2449
+ constructor(tableName, key) {
2450
+ this.tableName = tableName;
2451
+ this.key = key;
2452
+ }
2453
+ /**
2454
+ * Adds a condition that must be satisfied for the check to succeed.
2455
+ * Use this method when you need to:
2456
+ * - Validate complex item states
2457
+ * - Check multiple attributes together
2458
+ * - Ensure safety conditions are met
2459
+ *
2460
+ * @example
2461
+ * ```typescript
2462
+ * // Check dinosaur health and behavior
2463
+ * builder.condition(op =>
2464
+ * op.and([
2465
+ * op.gt('stats.health', 50),
2466
+ * op.not(op.eq('status', 'SEDATED')),
2467
+ * op.lt('aggressionLevel', 8)
2468
+ * ])
2469
+ * );
2470
+ *
2471
+ * // Verify habitat conditions
2472
+ * builder.condition(op =>
2473
+ * op.and([
2474
+ * op.eq('powerStatus', 'ONLINE'),
2475
+ * op.between('temperature', 20, 30),
2476
+ * op.attributeExists('lastMaintenance')
2477
+ * ])
2478
+ * );
2479
+ *
2480
+ * // Check breeding conditions
2481
+ * builder.condition(op =>
2482
+ * op.and([
2483
+ * op.eq('species', 'VELOCIRAPTOR'),
2484
+ * op.gte('age', 3),
2485
+ * op.eq('geneticPurity', 100)
2486
+ * ])
2487
+ * );
2488
+ * ```
2489
+ *
2490
+ * @param condition - Either a Condition object or a callback function that builds the condition
2491
+ * @returns The builder instance for method chaining
2492
+ */
2493
+ condition(condition) {
2494
+ if (typeof condition === "function") {
2495
+ const conditionOperator = {
2496
+ eq,
2497
+ ne,
2498
+ lt,
2499
+ lte,
2500
+ gt,
2501
+ gte,
2502
+ between,
2503
+ beginsWith,
2504
+ contains,
2505
+ attributeExists,
2506
+ attributeNotExists,
2507
+ and,
2508
+ or,
2509
+ not
2510
+ };
2511
+ this.conditionExpression = condition(conditionOperator);
2512
+ } else {
2513
+ this.conditionExpression = condition;
2514
+ }
2515
+ return this;
2516
+ }
2517
+ /**
2518
+ * Generates the DynamoDB command parameters for direct execution.
2519
+ * Use this method when you want to:
2520
+ * - Execute the condition check as a standalone operation
2521
+ * - Get the raw DynamoDB command for custom execution
2522
+ * - Inspect the generated command parameters
2523
+ *
2524
+ * @example
2525
+ * ```ts
2526
+ * const command = new ConditionCheckBuilder('myTable', { id: '123' })
2527
+ * .condition(op => op.attributeExists('status'))
2528
+ * .toDynamoCommand();
2529
+ * // Use command with DynamoDB client
2530
+ * ```
2531
+ *
2532
+ * @throws {Error} If no condition has been set
2533
+ * @returns The DynamoDB command parameters
2534
+ */
2535
+ toDynamoCommand() {
2536
+ if (!this.conditionExpression) {
2537
+ throw new Error("Condition is required for condition check operations");
2538
+ }
2539
+ const { expression, names, values } = prepareExpressionParams(this.conditionExpression);
2540
+ if (!expression) {
2541
+ throw new Error("Failed to generate condition expression");
2542
+ }
2543
+ return {
2544
+ tableName: this.tableName,
2545
+ key: this.key,
2546
+ conditionExpression: expression,
2547
+ expressionAttributeNames: names,
2548
+ expressionAttributeValues: values
2549
+ };
2550
+ }
2551
+ /**
2552
+ * Adds this condition check operation to a transaction.
2553
+ * Use this method when you need to:
2554
+ * - Verify habitat safety before transfers
2555
+ * - Ensure proper feeding conditions
2556
+ * - Validate security protocols
2557
+ *
2558
+ * @example
2559
+ * ```ts
2560
+ * const transaction = new TransactionBuilder();
2561
+ * new ConditionCheckBuilder('habitats', { id: 'PADDOCK-B' })
2562
+ * .condition(op => op.and([
2563
+ * op.eq('securityStatus', 'ACTIVE'),
2564
+ * op.lt('currentOccupants', 3),
2565
+ * op.eq('habitatType', 'CARNIVORE')
2566
+ * ]))
2567
+ * .withTransaction(transaction);
2568
+ * // Add dinosaur transfer operations
2569
+ * ```
2570
+ *
2571
+ * @param transaction - The transaction builder to add this operation to
2572
+ * @throws {Error} If no condition has been set
2573
+ * @returns The builder instance for method chaining
2574
+ */
2575
+ withTransaction(transaction) {
2576
+ if (!this.conditionExpression) {
2577
+ throw new Error("Condition is required for condition check operations");
2578
+ }
2579
+ const command = this.toDynamoCommand();
2580
+ transaction.conditionCheckWithCommand(command);
2581
+ return this;
2582
+ }
2583
+ /**
2584
+ * Gets a human-readable representation of the condition check command
2585
+ * with all expression placeholders replaced by their actual values.
2586
+ * Use this method when you need to:
2587
+ * - Debug complex condition expressions
2588
+ * - Verify condition parameters
2589
+ * - Log safety checks
2590
+ * - Troubleshoot condition failures
2591
+ *
2592
+ * @example
2593
+ * ```ts
2594
+ * const debugInfo = new ConditionCheckBuilder('dinosaurs', { id: 'TREX-001' })
2595
+ * .condition(op => op.and([
2596
+ * op.between('stats.health', 50, 100),
2597
+ * op.not(op.eq('status', 'SEDATED')),
2598
+ * op.attributeExists('lastFeedingTime')
2599
+ * op.eq('version', 1)
2600
+ * ]))
2601
+ * .debug();
2602
+ * console.log(debugInfo);
2603
+ * ```
2604
+ *
2605
+ * @returns A readable representation of the condition check command with resolved expressions
2606
+ */
2607
+ debug() {
2608
+ const command = this.toDynamoCommand();
2609
+ return debugCommand(command);
2610
+ }
2611
+ };
2612
+
2613
+ // src/builders/get-builder.ts
2614
+ var GetBuilder = class {
2615
+ /**
2616
+ * Creates a new GetBuilder instance.
2617
+ *
2618
+ * @param executor - Function that executes the get operation
2619
+ * @param key - Primary key of the item to retrieve
2620
+ * @param tableName - Name of the DynamoDB table
2621
+ */
2622
+ constructor(executor, key, tableName) {
2623
+ this.executor = executor;
2624
+ this.params = {
2625
+ tableName,
2626
+ key
2627
+ };
2628
+ }
2629
+ params;
2630
+ options = {};
2631
+ selectedFields = /* @__PURE__ */ new Set();
2632
+ /**
2633
+ * Specifies which attributes to return in the get results.
2634
+ * Use this method when you need to:
2635
+ * - Reduce data transfer by selecting specific dinosaur attributes
2636
+ * - Optimize response size for dinosaur records
2637
+ * - Focus on relevant dinosaur characteristics only
2638
+ *
2639
+ * @example
2640
+ * ```typescript
2641
+ * // Select single attribute
2642
+ * builder.select('species')
2643
+ *
2644
+ * // Select multiple attributes
2645
+ * builder.select(['id', 'species', 'diet'])
2646
+ *
2647
+ * // Chain multiple select calls
2648
+ * builder
2649
+ * .select('id')
2650
+ * .select(['species', 'diet'])
2651
+ * ```
2652
+ *
2653
+ * @param fields - A single field name or an array of field names to return
2654
+ * @returns The builder instance for method chaining
2655
+ */
2656
+ select(fields) {
2657
+ if (typeof fields === "string") {
2658
+ this.selectedFields.add(fields);
2659
+ } else if (Array.isArray(fields)) {
2660
+ for (const field of fields) {
2661
+ this.selectedFields.add(field);
2662
+ }
2663
+ }
2664
+ this.options.projection = Array.from(this.selectedFields);
2665
+ return this;
2666
+ }
2667
+ /**
2668
+ * Sets whether to use strongly consistent reads for the get operation.
2669
+ * Use this method when you need:
2670
+ * - The most up-to-date dinosaur data
2671
+ * - To ensure you're reading the latest dinosaur status
2672
+ * - Critical safety information about dangerous species
2673
+ *
2674
+ * Note: Consistent reads consume twice the throughput
2675
+ *
2676
+ * @example
2677
+ * ```typescript
2678
+ * // Get the latest T-Rex data
2679
+ * const result = await new GetBuilder(executor, { pk: 'dinosaur#123', sk: 'profile' })
2680
+ * .consistentRead()
2681
+ * .execute();
2682
+ * ```
2683
+ *
2684
+ * @param consistentRead - Whether to use consistent reads (defaults to true)
2685
+ * @returns The builder instance for method chaining
2686
+ */
2687
+ consistentRead(consistentRead = true) {
2688
+ this.params.consistentRead = consistentRead;
2689
+ return this;
2690
+ }
2691
+ /**
2692
+ * Executes the get operation against DynamoDB.
2693
+ *
2694
+ * @example
2695
+ * ```typescript
2696
+ * try {
2697
+ * const result = await new GetBuilder(executor, { pk: 'dinosaur#123', sk: 'profile' })
2698
+ * .select(['species', 'name', 'diet'])
2699
+ * .consistentRead()
2700
+ * .execute();
2701
+ *
2702
+ * if (result.item) {
2703
+ * console.log('Dinosaur found:', result.item);
2704
+ * } else {
2705
+ * console.log('Dinosaur not found');
2706
+ * }
2707
+ * } catch (error) {
2708
+ * console.error('Error getting dinosaur:', error);
2709
+ * }
2710
+ * ```
2711
+ *
2712
+ * @returns A promise that resolves to an object containing:
2713
+ * - item: The retrieved dinosaur or undefined if not found
2714
+ */
2715
+ async execute() {
2716
+ if (this.selectedFields.size > 0) {
2717
+ const expressionAttributeNames = {};
2718
+ const projectionParts = [];
2719
+ for (const path of this.selectedFields) {
2720
+ const attrName = `#attr${projectionParts.length}`;
2721
+ expressionAttributeNames[attrName] = path;
2722
+ projectionParts.push(attrName);
2723
+ }
2724
+ this.params.projectionExpression = projectionParts.join(", ");
2725
+ this.params.expressionAttributeNames = expressionAttributeNames;
2726
+ }
2727
+ return this.executor(this.params);
2728
+ }
2729
+ };
2730
+
2731
+ // src/builders/scan-builder.ts
2732
+ var ScanBuilder = class _ScanBuilder extends FilterBuilder {
2733
+ executor;
2734
+ constructor(executor) {
2735
+ super();
2736
+ this.executor = executor;
2737
+ }
2738
+ /**
2739
+ * Creates a deep clone of this ScanBuilder instance.
2740
+ * Use this method when you need to:
2741
+ * - Create scan templates
2742
+ * - Run multiple variations of a scan
2743
+ * - Implement pagination (used internally by paginate())
2744
+ *
2745
+ * @returns A new ScanBuilder instance with the same configuration
2746
+ */
2747
+ clone() {
2748
+ const clone = new _ScanBuilder(this.executor);
2749
+ clone.options = { ...this.options };
2750
+ clone.selectedFields = new Set(this.selectedFields);
2751
+ return clone;
2752
+ }
2753
+ /**
2754
+ * Executes the scan against DynamoDB.
2755
+ * Use this method when you need to:
2756
+ * - Search across the entire table
2757
+ * - Find items matching specific criteria
2758
+ * - Perform full table analysis
2759
+ * - Generate reports across all data
2760
+ *
2761
+ * The method returns both the matched items and, if there are more results,
2762
+ * a lastEvaluatedKey that can be used with startFrom() to continue the scan.
2763
+ *
2764
+ * @example
2765
+ * ```typescript
2766
+ * try {
2767
+ * // Find all dinosaurs with high aggression levels
2768
+ * const result = await new ScanBuilder(executor)
2769
+ * .filter(op =>
2770
+ * op.and([
2771
+ * op.eq('status', 'ACTIVE'),
2772
+ * op.gt('aggressionLevel', 7)
2773
+ * ])
2774
+ * )
2775
+ * .limit(20)
2776
+ * .execute();
2777
+ *
2778
+ * console.log(`Found ${result.items.length} potentially dangerous dinosaurs`);
2779
+ *
2780
+ * if (result.lastEvaluatedKey) {
2781
+ * console.log('More results available');
2782
+ * }
2783
+ * } catch (error) {
2784
+ * console.error('Security scan failed:', error);
2785
+ * }
2786
+ * ```
2787
+ *
2788
+ * @returns A promise that resolves to an object containing:
2789
+ * - items: Array of items matching the scan criteria
2790
+ * - lastEvaluatedKey: Token for continuing the scan, if more items exist
2791
+ */
2792
+ async execute() {
2793
+ return this.executor(this.options);
2794
+ }
2795
+ };
2796
+
2797
+ // src/table.ts
2798
+ var DDB_BATCH_WRITE_LIMIT = 25;
2799
+ var DDB_BATCH_GET_LIMIT = 100;
2800
+ var Table = class {
2801
+ dynamoClient;
2802
+ tableName;
2803
+ /**
2804
+ * The column name of the partitionKey for the Table
2805
+ */
2806
+ partitionKey;
2807
+ /**
2808
+ * The column name of the sortKey for the Table
2809
+ */
2810
+ sortKey;
2811
+ /**
2812
+ * The Global Secondary Indexes that are configured on this table
2813
+ */
2814
+ gsis;
2815
+ constructor(config) {
2816
+ this.dynamoClient = config.client;
2817
+ this.tableName = config.tableName;
2818
+ this.partitionKey = config.indexes.partitionKey;
2819
+ this.sortKey = config.indexes.sortKey;
2820
+ this.gsis = config.indexes.gsis || {};
2821
+ }
2822
+ /**
2823
+ * Creates a new item in the table, it will fail if the item already exists
2824
+ *
2825
+ * @param item The item to create
2826
+ * @returns A PutBuilder instance for chaining conditions and executing the put operation
2827
+ */
2828
+ create(item) {
2829
+ return this.put(item).condition((op) => op.attributeNotExists(this.partitionKey));
2830
+ }
2831
+ get(keyCondition) {
2832
+ const executor = async (params) => {
2833
+ try {
2834
+ const result = await this.dynamoClient.get({
2835
+ TableName: params.tableName,
2836
+ Key: params.key,
2837
+ ProjectionExpression: params.projectionExpression,
2838
+ ExpressionAttributeNames: params.expressionAttributeNames,
2839
+ ConsistentRead: params.consistentRead
2840
+ });
2841
+ return {
2842
+ item: result.Item ? result.Item : void 0
2843
+ };
2844
+ } catch (error) {
2845
+ console.error("Error getting item:", error);
2846
+ throw error;
2847
+ }
2848
+ };
2849
+ return new GetBuilder(executor, keyCondition, this.tableName);
2850
+ }
2851
+ /**
2852
+ * Updates an item in the table
2853
+ *
2854
+ * @param item The item to update
2855
+ * @returns A PutBuilder instance for chaining conditions and executing the put operation
2856
+ */
2857
+ put(item) {
2858
+ const executor = async (params) => {
2859
+ try {
2860
+ const result = await this.dynamoClient.put({
2861
+ TableName: params.tableName,
2862
+ Item: params.item,
2863
+ ConditionExpression: params.conditionExpression,
2864
+ ExpressionAttributeNames: params.expressionAttributeNames,
2865
+ ExpressionAttributeValues: params.expressionAttributeValues,
2866
+ // CONSISTENT is not a valid ReturnValue for DDB, so we set NONE as we are not interested in its
2867
+ // response and will be reloading the item from the DB through a get instead
2868
+ ReturnValues: params.returnValues === "CONSISTENT" ? "NONE" : params.returnValues
2869
+ });
2870
+ if (params.returnValues === "CONSISTENT") {
2871
+ const key = {
2872
+ pk: params.item[this.partitionKey],
2873
+ ...this.sortKey && { sk: params.item[this.sortKey] }
2874
+ };
2875
+ const getResult = await this.dynamoClient.get({
2876
+ TableName: params.tableName,
2877
+ Key: key,
2878
+ ConsistentRead: true
2879
+ });
2880
+ return getResult.Item;
2881
+ }
2882
+ return result.Attributes;
2883
+ } catch (error) {
2884
+ console.error("Error creating item:", error);
2885
+ throw error;
2886
+ }
2887
+ };
2888
+ return new PutBuilder(executor, item, this.tableName);
2889
+ }
2890
+ /**
2891
+ * Creates a query builder for complex queries
2892
+ * If useIndex is called on the returned QueryBuilder, it will use the GSI configuration
2893
+ */
2894
+ query(keyCondition) {
2895
+ const pkAttributeName = this.partitionKey;
2896
+ const skAttributeName = this.sortKey;
2897
+ let keyConditionExpression = eq(pkAttributeName, keyCondition.pk);
2898
+ if (keyCondition.sk) {
2899
+ if (!skAttributeName) {
2900
+ throw new Error("Sort key is not defined for Index");
2901
+ }
2902
+ const keyConditionOperator = {
2903
+ eq: (value) => eq(skAttributeName, value),
2904
+ lt: (value) => lt(skAttributeName, value),
2905
+ lte: (value) => lte(skAttributeName, value),
2906
+ gt: (value) => gt(skAttributeName, value),
2907
+ gte: (value) => gte(skAttributeName, value),
2908
+ between: (lower, upper) => between(skAttributeName, lower, upper),
2909
+ beginsWith: (value) => beginsWith(skAttributeName, value),
2910
+ and: (...conditions) => and(...conditions)
2911
+ };
2912
+ const skCondition = keyCondition.sk(keyConditionOperator);
2913
+ keyConditionExpression = and(eq(pkAttributeName, keyCondition.pk), skCondition);
2914
+ }
2915
+ const executor = async (originalKeyCondition, options) => {
2916
+ let finalKeyCondition = originalKeyCondition;
2917
+ if (options.indexName) {
2918
+ const gsiName = String(options.indexName);
2919
+ const gsi = this.gsis[gsiName];
2920
+ if (!gsi) {
2921
+ throw new Error(`GSI with name "${gsiName}" does not exist on table "${this.tableName}"`);
2922
+ }
2923
+ const gsiPkAttributeName = gsi.partitionKey;
2924
+ const gsiSkAttributeName = gsi.sortKey;
2925
+ let pkValue;
2926
+ let skValue;
2927
+ let extractedSkCondition;
2928
+ if (originalKeyCondition.type === "eq") {
2929
+ pkValue = originalKeyCondition.value;
2930
+ } else if (originalKeyCondition.type === "and" && originalKeyCondition.conditions) {
2931
+ const pkCondition = originalKeyCondition.conditions.find(
2932
+ (c) => c.type === "eq" && c.attr === pkAttributeName
2933
+ );
2934
+ if (pkCondition && pkCondition.type === "eq") {
2935
+ pkValue = pkCondition.value;
2936
+ }
2937
+ const skConditions = originalKeyCondition.conditions.filter((c) => c.attr === skAttributeName);
2938
+ if (skConditions.length > 0) {
2939
+ if (skConditions.length === 1) {
2940
+ extractedSkCondition = skConditions[0];
2941
+ if (extractedSkCondition && extractedSkCondition.type === "eq") {
2942
+ skValue = extractedSkCondition.value;
2943
+ }
2944
+ } else if (skConditions.length > 1) {
2945
+ extractedSkCondition = and(...skConditions);
2946
+ }
2947
+ }
2948
+ }
2949
+ if (!pkValue) {
2950
+ throw new Error("Could not extract partition key value from key condition");
2951
+ }
2952
+ let gsiKeyCondition = eq(gsiPkAttributeName, pkValue);
2953
+ if (skValue && gsiSkAttributeName) {
2954
+ gsiKeyCondition = and(gsiKeyCondition, eq(gsiSkAttributeName, skValue));
2955
+ } else if (extractedSkCondition && gsiSkAttributeName) {
2956
+ if (extractedSkCondition.attr === skAttributeName) {
2957
+ const updatedSkCondition = {
2958
+ ...extractedSkCondition,
2959
+ attr: gsiSkAttributeName
2960
+ };
2961
+ gsiKeyCondition = and(gsiKeyCondition, updatedSkCondition);
2962
+ } else {
2963
+ gsiKeyCondition = and(gsiKeyCondition, extractedSkCondition);
2964
+ }
2965
+ }
2966
+ finalKeyCondition = gsiKeyCondition;
2967
+ }
2968
+ const expressionParams = {
2969
+ expressionAttributeNames: {},
2970
+ expressionAttributeValues: {},
2971
+ valueCounter: { count: 0 }
2972
+ };
2973
+ const keyConditionExpression2 = buildExpression(finalKeyCondition, expressionParams);
2974
+ let filterExpression;
2975
+ if (options.filter) {
2976
+ filterExpression = buildExpression(options.filter, expressionParams);
2977
+ }
2978
+ const projectionExpression = options.projection?.map((p) => generateAttributeName(expressionParams, p)).join(", ");
2979
+ const { expressionAttributeNames, expressionAttributeValues } = expressionParams;
2980
+ const { indexName, limit, consistentRead, scanIndexForward, lastEvaluatedKey } = options;
2981
+ const params = {
2982
+ TableName: this.tableName,
2983
+ KeyConditionExpression: keyConditionExpression2,
2984
+ FilterExpression: filterExpression,
2985
+ ExpressionAttributeNames: expressionAttributeNames,
2986
+ ExpressionAttributeValues: expressionAttributeValues,
2987
+ IndexName: indexName,
2988
+ Limit: limit,
2989
+ ConsistentRead: consistentRead,
2990
+ ScanIndexForward: scanIndexForward,
2991
+ ProjectionExpression: projectionExpression,
2992
+ ExclusiveStartKey: lastEvaluatedKey
2993
+ };
2994
+ try {
2995
+ const result = await this.dynamoClient.query(params);
2996
+ return {
2997
+ items: result.Items,
2998
+ lastEvaluatedKey: result.LastEvaluatedKey
2999
+ };
3000
+ } catch (error) {
3001
+ console.log(debugCommand(params));
3002
+ console.error("Error querying items:", error);
3003
+ throw error;
3004
+ }
3005
+ };
3006
+ return new QueryBuilder(executor, keyConditionExpression);
3007
+ }
3008
+ /**
3009
+ * Creates a scan builder for scanning the entire table
3010
+ * Use this when you need to:
3011
+ * - Process all items in a table
3012
+ * - Apply filters to a large dataset
3013
+ * - Use a GSI for scanning
3014
+ *
3015
+ * @returns A ScanBuilder instance for chaining operations
3016
+ */
3017
+ scan() {
3018
+ const executor = async (options) => {
3019
+ const expressionParams = {
3020
+ expressionAttributeNames: {},
3021
+ expressionAttributeValues: {},
3022
+ valueCounter: { count: 0 }
3023
+ };
3024
+ let filterExpression;
3025
+ if (options.filter) {
3026
+ filterExpression = buildExpression(options.filter, expressionParams);
3027
+ }
3028
+ const projectionExpression = options.projection?.map((p) => generateAttributeName(expressionParams, p)).join(", ");
3029
+ const { expressionAttributeNames, expressionAttributeValues } = expressionParams;
3030
+ const { indexName, limit, consistentRead, lastEvaluatedKey } = options;
3031
+ const params = {
3032
+ TableName: this.tableName,
3033
+ FilterExpression: filterExpression,
3034
+ ExpressionAttributeNames: Object.keys(expressionAttributeNames).length > 0 ? expressionAttributeNames : void 0,
3035
+ ExpressionAttributeValues: Object.keys(expressionAttributeValues).length > 0 ? expressionAttributeValues : void 0,
3036
+ IndexName: indexName,
3037
+ Limit: limit,
3038
+ ConsistentRead: consistentRead,
3039
+ ProjectionExpression: projectionExpression,
3040
+ ExclusiveStartKey: lastEvaluatedKey
3041
+ };
3042
+ try {
3043
+ const result = await this.dynamoClient.scan(params);
3044
+ return {
3045
+ items: result.Items,
3046
+ lastEvaluatedKey: result.LastEvaluatedKey
3047
+ };
3048
+ } catch (error) {
3049
+ console.log(debugCommand(params));
3050
+ console.error("Error scanning items:", error);
3051
+ throw error;
3052
+ }
3053
+ };
3054
+ return new ScanBuilder(executor);
3055
+ }
3056
+ delete(keyCondition) {
3057
+ const executor = async (params) => {
3058
+ try {
3059
+ const result = await this.dynamoClient.delete({
3060
+ TableName: params.tableName,
3061
+ Key: params.key,
3062
+ ConditionExpression: params.conditionExpression,
3063
+ ExpressionAttributeNames: params.expressionAttributeNames,
3064
+ ExpressionAttributeValues: params.expressionAttributeValues,
3065
+ ReturnValues: params.returnValues
3066
+ });
3067
+ return {
3068
+ item: result.Attributes
3069
+ };
3070
+ } catch (error) {
3071
+ console.error("Error deleting item:", error);
3072
+ throw error;
3073
+ }
3074
+ };
3075
+ return new DeleteBuilder(executor, this.tableName, keyCondition);
3076
+ }
3077
+ /**
3078
+ * Updates an item in the table
3079
+ *
3080
+ * @param keyCondition The primary key of the item to update
3081
+ * @returns An UpdateBuilder instance for chaining update operations and conditions
3082
+ */
3083
+ update(keyCondition) {
3084
+ const executor = async (params) => {
3085
+ try {
3086
+ const result = await this.dynamoClient.update({
3087
+ TableName: params.tableName,
3088
+ Key: params.key,
3089
+ UpdateExpression: params.updateExpression,
3090
+ ConditionExpression: params.conditionExpression,
3091
+ ExpressionAttributeNames: params.expressionAttributeNames,
3092
+ ExpressionAttributeValues: params.expressionAttributeValues,
3093
+ ReturnValues: params.returnValues
3094
+ });
3095
+ return {
3096
+ item: result.Attributes
3097
+ };
3098
+ } catch (error) {
3099
+ console.error("Error updating item:", error);
3100
+ throw error;
3101
+ }
3102
+ };
3103
+ return new UpdateBuilder(executor, this.tableName, keyCondition);
3104
+ }
3105
+ /**
3106
+ * Creates a transaction builder for performing multiple operations atomically
3107
+ */
3108
+ transactionBuilder() {
3109
+ const executor = async (params) => {
3110
+ await this.dynamoClient.transactWrite(params);
3111
+ };
3112
+ return new TransactionBuilder(executor, {
3113
+ partitionKey: this.partitionKey,
3114
+ sortKey: this.sortKey
3115
+ });
3116
+ }
3117
+ /**
3118
+ * Executes a transaction using a callback function
3119
+ *
3120
+ * @param callback A function that receives a transaction context and performs operations on it
3121
+ * @param options Optional transaction options
3122
+ * @returns A promise that resolves when the transaction is complete
3123
+ */
3124
+ transaction(callback, options) {
3125
+ const executor = async () => {
3126
+ const transactionExecutor = async (params) => {
3127
+ await this.dynamoClient.transactWrite(params);
3128
+ };
3129
+ const transaction = new TransactionBuilder(transactionExecutor, {
3130
+ partitionKey: this.partitionKey,
3131
+ sortKey: this.sortKey
3132
+ });
3133
+ if (options) {
3134
+ transaction.withOptions(options);
3135
+ }
3136
+ const result = await callback(transaction);
3137
+ await transaction.execute();
3138
+ return result;
3139
+ };
3140
+ return executor();
3141
+ }
3142
+ /**
3143
+ * Creates a condition check operation for use in transactions
3144
+ *
3145
+ * This is useful for when you require a transaction to succeed only when a specific condition is met on a
3146
+ * a record within the database that you are not directly updating.
3147
+ *
3148
+ * For example, you are updating a record and you want to ensure that another record exists and/or has a specific value before proceeding.
3149
+ */
3150
+ conditionCheck(keyCondition) {
3151
+ return new ConditionCheckBuilder(this.tableName, keyCondition);
3152
+ }
3153
+ /**
3154
+ * Performs a batch get operation to retrieve multiple items at once
3155
+ *
3156
+ * @param keys Array of primary keys to retrieve
3157
+ * @returns A promise that resolves to the retrieved items
3158
+ */
3159
+ async batchGet(keys) {
3160
+ const allItems = [];
3161
+ const allUnprocessedKeys = [];
3162
+ for (const chunk of chunkArray(keys, DDB_BATCH_GET_LIMIT)) {
3163
+ const formattedKeys = chunk.map((key) => ({
3164
+ [this.partitionKey]: key.pk,
3165
+ ...this.sortKey ? { [this.sortKey]: key.sk } : {}
3166
+ }));
3167
+ const params = {
3168
+ RequestItems: {
3169
+ [this.tableName]: {
3170
+ Keys: formattedKeys
3171
+ }
3172
+ }
3173
+ };
3174
+ try {
3175
+ const result = await this.dynamoClient.batchGet(params);
3176
+ if (result.Responses?.[this.tableName]) {
3177
+ allItems.push(...result.Responses[this.tableName]);
3178
+ }
3179
+ const unprocessedKeysArray = result.UnprocessedKeys?.[this.tableName]?.Keys || [];
3180
+ const unprocessedKeys = unprocessedKeysArray.map((key) => ({
3181
+ pk: key[this.partitionKey],
3182
+ sk: this.sortKey ? key[this.sortKey] : void 0
3183
+ }));
3184
+ if (unprocessedKeys.length > 0) {
3185
+ allUnprocessedKeys.push(...unprocessedKeys);
3186
+ }
3187
+ } catch (error) {
3188
+ console.error("Error in batch get operation:", error);
3189
+ throw error;
3190
+ }
3191
+ }
3192
+ return {
3193
+ items: allItems,
3194
+ unprocessedKeys: allUnprocessedKeys
3195
+ };
3196
+ }
3197
+ /**
3198
+ * Performs a batch write operation to put or delete multiple items at once
3199
+ *
3200
+ * @param operations Array of put or delete operations
3201
+ * @returns A promise that resolves to any unprocessed operations
3202
+ */
3203
+ async batchWrite(operations) {
3204
+ const allUnprocessedItems = [];
3205
+ for (const chunk of chunkArray(operations, DDB_BATCH_WRITE_LIMIT)) {
3206
+ const writeRequests = chunk.map((operation) => {
3207
+ if (operation.type === "put") {
3208
+ return {
3209
+ PutRequest: {
3210
+ Item: operation.item
3211
+ }
3212
+ };
3213
+ }
3214
+ return {
3215
+ DeleteRequest: {
3216
+ Key: {
3217
+ [this.partitionKey]: operation.key.pk,
3218
+ ...this.sortKey ? { [this.sortKey]: operation.key.sk } : {}
3219
+ }
3220
+ }
3221
+ };
3222
+ });
3223
+ const params = {
3224
+ RequestItems: {
3225
+ [this.tableName]: writeRequests
3226
+ }
3227
+ };
3228
+ try {
3229
+ const result = await this.dynamoClient.batchWrite(params);
3230
+ const unprocessedRequestsArray = result.UnprocessedItems?.[this.tableName] || [];
3231
+ if (unprocessedRequestsArray.length > 0) {
3232
+ const unprocessedItems = unprocessedRequestsArray.map((request) => {
3233
+ if (request?.PutRequest?.Item) {
3234
+ return {
3235
+ type: "put",
3236
+ item: request.PutRequest.Item
3237
+ };
3238
+ }
3239
+ if (request?.DeleteRequest?.Key) {
3240
+ return {
3241
+ type: "delete",
3242
+ key: {
3243
+ pk: request.DeleteRequest.Key[this.partitionKey],
3244
+ sk: this.sortKey ? request.DeleteRequest.Key[this.sortKey] : void 0
3245
+ }
3246
+ };
3247
+ }
3248
+ throw new Error("Invalid unprocessed item format returned from DynamoDB");
3249
+ });
3250
+ allUnprocessedItems.push(...unprocessedItems);
3251
+ }
3252
+ } catch (error) {
3253
+ console.error("Error in batch write operation:", error);
3254
+ throw error;
3255
+ }
3256
+ }
3257
+ return {
3258
+ unprocessedItems: allUnprocessedItems
3259
+ };
3260
+ }
3261
+ };
3262
+
3263
+ exports.Table = Table;
3264
+ //# sourceMappingURL=table.cjs.map
3265
+ //# sourceMappingURL=table.cjs.map