dyno-table 2.6.0 → 2.6.1

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