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