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