dyno-table 2.6.0 → 2.6.2

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