dyno-table 2.6.0 → 2.6.1

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