dyno-table 1.4.0 → 1.6.0

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