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