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